Table of Contents
This document describes overview of security provisions defined in Financial-grade API Security Profile 1.0 - Part 2: Advanced (hereinafter called “FAPI”) and configuration instructions of Authlete through steps for building a FAPI compliant authorization server.
It is strongly advised that you have basic knowledge of OpenID Connect and Authlete by doing the following tutorial.
In this tutorial, we assume the following components. Note that only Authlete’s consoles and APIs are up and running, while an authorization server (OIDC identity provider) and a resource server don’t actually exist.
Instead you will use curl command to simulate how these servers make API requests to Authlete on receiving authorization requests, token requests and token introspection requests from clients (OIDC relying party).
FODNs for each component are as follows. The authorization server and the client don’t exist as stated above, but their FQDNs are at least needed to explain the OAuth flow.
Component | FQDN |
---|---|
Authlete API | api.authlete.com |
Authlete Service Owner Console | so.authlete.com |
Authlete Developer Console | cd.authlete.com |
Authorization Server (OIDC Identity Provider) | as.example.com |
Client (OIDC Relying Party) | client.example.org |
Resource Server | N/A |
In this document, you will configure Authlete to enable the following token granting process and API access flows in a FAPI-compliant manner.
A FAPI-compliant client has to employ a request object to craft an authorization request to a FAPI-compliant authorization server.
The request object is passed to the server by either value (using request
parameter) or reference (request_uri
parameter). In this document, the client will be using the former one.
In addition to the request object, you have to comply with other security provisions of FAPI, such as response_type
parameter. In this document, the client will be using response_type=code id_token
.
response_type=code
.
Other settings, including claims
, are to be explained later.
In this document, the server will be making a response include the code
as a fragment since response_type=code id_token
is specified in the previous step.
An authorization server has to employ public key methods to authenticate clients on receiving a token request. In this document, the server will be using mutual TLS authentication.
An authorization server has to bind an access token with a client certificate obtained from mutual TLS communication at token endpoint of the server, and make a token response to the client.
An client and a resource server have to establish a mutual TLS communication. The resource server will be verifying the binding between the client certificate and the access token in an API request and then making a response if the binding is valid.
Log into Authlete’s Service Owner Console https://so.authlete.com/
and click “Create Service” button. You will see the service creation page. Enter Service Name and Token Issuer Identifier as follows, and click “Create” button.
Press “OK” in a dialog for confirmation.
Item | Value |
---|---|
Service Name | An arbitrary value e.g. FAPI Service |
Token Issuer Identifier | https://as.example.com |
The new service has been created. Automatically generated values of “API Key” and “API Secret” will be used as “Login ID” and “Password” to log in to Developer Console, as well as credential for your authorization server to make requests to Authlete APIs.
Item | Value |
---|---|
API Key | Auto-generated e.g. 174381609020 |
API Secret | Auto-generated e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k |
Let’s enable FAPI support of the service. Click “Edit” button in the bottom of the page to make settings editable and go to Authorization tab. There should be FAPI with a checkbox, which is unchecked, on Supported Service Profiles. Check the box and click “Update” button in the bottom of the page. Press “OK” in a dialog for confirmation.
Click “Edit” button to make settings editable again, and go to Token tab. There should be Supported Scopes and some predefined scopes such as address
and openid
.
Click “Create Scope” button on the right side so that you can see a dialog to define a scope.
Add a new scope with the following parameters.
fapi
and value: rw
. Read the KB article for the scope attributes feature.
Item | Value |
---|---|
Scope Name | payment |
Default Entry | Choose Non default |
Description | An arbitrary value |
Attributes (Click “New Attribute” button to add) | Key: fapi / Value: rw |
Open the link to Authlete’s Developer Console for the service (https://cd.authlete.com/<API Key>
e.g. https://cd.authlete.com/174381609020
) and log in to the console with your API Key and API Secret as Login ID and Password respectively.
Click “Create App” button on the right side and enter/choose the following values (Client Name and Client Type) in Basic tab.
Item | Value |
---|---|
Client Name | An arbitrary value e.g. FAPI Client |
Client Type | Choose CONFIDENTIAL |
Then click Authorization tab next to the Basic, click Create Redirect URI button in Redirect URIs section, and enter the following value for a New Redirect URI.
Item | Value |
---|---|
Redirect URIs | https://client.example.org/cb/example.com |
Click “Create” button in the bottom of the page. Press “OK” in a dialog for confirmation.
Now you’ve done registration of the client to the service.
Automatically generated values of “Client ID” will be used as client_id
for the client to make requests to the authorization server. “Client Secret” is generated as well, but it is never used in this tutorial.
Also make sure other values are set as expected.
Item | Value |
---|---|
Client ID | Auto-generetad e.g. 591205987816490 |
Client Type | CONFIDENTIAL |
Redirect URIs | https://client.example.org/cb/example.com |
Let’s check how Authlete works with the initial configuration. We will use /auth/authorization
API for that purpose.
Let’s assume that the auhtorization server receives an authorization request (scope=openid
) that doesn’t include any scopes subject to FAPI. The server will make a request to /auth/authorization
API. Here’s a curl version of the request.
Make sure to replace API Key
, API Secret
, Client ID
by your own values generated in the previous step.
curl -s -X POST https://api.authlete.com/api/auth/authorization \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid&response_type=code&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj"}' |jq
Make sure to replace API Key
, API Secret
, Client ID
by your own values generated in the previous step.
If you are using Windows 10's bundled curl.exe command via PowerShell, make sure the command is curl.exe
instead of curl
, escape "
characters and use `
to break lines.
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"parameters\": \"redirect_uri=https://client.example.org/cb/example.com&scope=openid&response_type=code&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj\"}'
Authlete makes the following response (folded for readability).
{
"type": "authorizationResponse",
"resultCode": "A004001",
"resultMessage": "[A004001] Authlete has successfully issued a ticket
to the service (API Key = 174381609020) for the authorization request
from the client (ID = 591205987816490). [response_type=code, openid=true]",
[...]
According to the value of resultMessage
, Authlete accepted the authorization request when its scopes are not FAPI-related. So what happens if the request includes FAPI scopes? Let’s try it in the next section.
Let’s assume the authorization server receives an authorization request (scope=openid payment
) that does include a scope (payment
) subject to FAPI. The server will make a request to /auth/authorization
API. Here’s a curl version of the request.
curl -s -X POST https://api.authlete.com/api/auth/authorization \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj"}' |jq
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"parameters\": \"redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj\"}'
Authlete makes the following error response (folded for readability).
{
"type": "authorizationResponse",
"resultCode": "A150312",
"resultMessage": "[A150312] The value of 'response_type' (code) is not allowed.",
[...]
According to the value of resultMessage
, response_type=code
is not allowed.
This is due to security provisions of FAPI. 5.2. Advanced security provisions / 5.2.2. Authorization server states as follows.
- shall require
- the
response_type
valuecode id_token
, or- the
response_type
valuecode
in conjunction with theresponse_mode
valuejwt
;
Thus you have to use either response_type=code id_token
, or response_type=code
with response_mode=jwt
in a FAPI-compliant environment. In other words, response_type=code
without the response_mode=jwt
parameter is prohibited i.e. an authorization server must not accept them.
So, how about an authorization request that includes response_type=code id_token
instead of response_type=code
?
Let’s make a request to /auth/authorization
API as follows.
curl -s -X POST https://api.authlete.com/api/auth/authorization \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj"}' |jq
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"parameters\": \"redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj\"}'
Again Authlete makes another error response.
{
"type": "authorizationResponse",
"resultCode": "A150301",
"resultMessage": "[A150301] A request object is required.",
[...]
According to the value of resultMessage
, a request object is required to proceed.
This is due to security provisions of FAPI. The same section, 5.2. Advanced security provisions / 5.2.2. Authorization server states as follows.
- shall only use the parameters included in the signed request object passed via the
request
orrequest_uri
parameter;
An authorization server has to mandate clients to pass a request object by value (request
) or by reference (request_uri
).
So let’s craft an authorization request with a request object in the next section to make the request FAPI-compliant.
In this section, we will create an authorization request using a a request object.
We have to prepare a key for a client to sign to a request object. In this document, we will use mkjwk to create an ES256 key pair, and two types of key sets; One includes a private key and other one doesn’t.
Parameters for mkjwk are as follows.
Item | Value |
---|---|
Tab | EC (Elliptic Curve) |
Curve | P-256 |
Key Use | Signing |
Algorithm | ES256 (ECDSA using P-256 and SHA-256) |
Key ID | An arbitrary value e.g. 1 |
Here are examples of a generated key set and a derived set without a private key.
es256_keyset.txt
(including a row of a private key "d"
){
"keys": [
{
"kty": "EC",
"d": "L6KxA-db4oh5NKYEpO6IulUDSRXP7fqNAmScu6fygIE",
"use": "sig",
"crv": "P-256",
"kid": "1",
"x": "icP8p_AigyTzwSpLRyv_bBQTSGu_NG7pMVXd-RAxwYE",
"y": "06tC0MJeBlZNYlnY8g4bCA9wJ34XN-rWfWlmmlhf-F0",
"alg": "ES256"
}
]
}
es256_keyset_pub.txt
(excluding a row of a private key "d"
from es256_keyset.txt
){
"keys": [
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"kid": "1",
"x": "icP8p_AigyTzwSpLRyv_bBQTSGu_NG7pMVXd-RAxwYE",
"y": "06tC0MJeBlZNYlnY8g4bCA9wJ34XN-rWfWlmmlhf-F0",
"alg": "ES256"
}
]
}
In order for Authlete service to verify a signature of a request object coming from a client, you have to register its public key to the client’s settings and specify a signing algorithm that the client uses.
Log into Developer Console for the service (https://cd.authlete.com/<API Key>
e.g. https://cd.authlete.com/174381609020
) and configure the client’s settings as follows.
Item | Value |
---|---|
JWK Set Content | Paste content of es256_keyset_pub.txt |
Item | Value |
---|---|
Request Object Signature Algorithm | ES256 |
Now we have finished preparation to have Authlete verify a signature of a request object.
Let’s act as a client generating a request object and crafting an authorization request with the object.
Here is a sample payload in this document. Enter the correct values of client_id
, nbf
and exp
.
payload.txt
{
"redirect_uri":"https://client.example.org/cb/example.com",
"response_type":"code id_token",
"client_id":"<client_id> e.g. 591205987816490",
"scope":"openid payment",
"nbf": <Unix time of the current time> e.g. 1613373232,
"exp": <nbf + 3600> e.g. 1613376832,
"exp":15549730000,
"aud":"https://as.example.com",
"nonce":"n-0S6_WzA2Mj"
}
nbf
and exp
.
nbf
claim: get the current Unix time using some method (e.g. date +%s
) and add it to the payload as a value of nbf
claims. For example, if you would get a value 1613373232
as a result of date +%s
,
you would add the following line to payload.txt
.
"nbf":1613373232,
exp
claim: specify a new value which is a sum of the value of nbf
and 3600
. For example, if "nbf":1613373232
would be used, the new value would be 1613373232 + 3600 = 1613376832
and the line of the exp
claim in the payload.txt
would be as follows.
"exp":1613376832,
The client will be creating a signed JWT with the private key generated in the previous section. In this document, we use mkjose for example. Enter/choose values for each item and click “Generate” so that you can find the signed JWT in Output section.
Item | Value |
---|---|
Payload | Paste content of payload.txt |
Signlng Algorithm | Choose EC256 |
Signing Key | Remove the first two lines ("keys": , [ ) and the last two lines (] , } ) from the content of es256_keyset.txt (as it must be a JWK, not a JWK set) and paste it |
Another example using step CLI is as follows.
cat payload.txt | \
step crypto jws sign --jwks=es256_keyset.txt --kid=1
In this document, the following result was made. This will be used as a value of request
parameter in an authorizaiton request.
eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoibm9uY2UiOiJuLTBTNl9XekEyTWoiCn0K.q_MbfV5qN-gnB93JaQGVrEXu8WvhDuUzWx6DwC50J8AiQGjXDEpw9satUAMN18rrgnGNciiFztoEFJuJjrJoyA
Let’s assume that the auhtorization server receives an authorization request,
including a request object, from a client.
The server will make a request to /auth/authorization
API. Here’s a curl version of the request.
curl -s -X POST https://api.authlete.com/api/auth/authorization \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj&request=<Request Object e.g. eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoibm9uY2UiOiJuLTBTNl9XekEyTWoiCn0K.q_MbfV5qN-gnB93JaQGVrEXu8WvhDuUzWx6DwC50J8AiQGjXDEpw9satUAMN18rrgnGNciiFztoEFJuJjrJoyA>"}' |jq
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"parameters\": \"redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj&request=<Request Object e.g. eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoibm9uY2UiOiJuLTBTNl9XekEyTWoiCn0K.q_MbfV5qN-gnB93JaQGVrEXu8WvhDuUzWx6DwC50J8AiQGjXDEpw9satUAMN18rrgnGNciiFztoEFJuJjrJoyA>\"}'
Authlete will make a response (folded for readability) like this.
{
"type": "authorizationResponse",
"resultCode": "A004001",
"resultMessage": "[A004001] Authlete has successfully issued a ticket to the service
(API Key = 174381609020) for the authorization request from the client
(ID = 591205987816490). [response_type=code id_token, openid=true]",
[...]
"ticket": "rjasCNvemUwamKP0G1h9Fh5Uo_3fgBfQsTF1PU4-GiE"
[...]
According to the value of resultMessage
, Authlete accepted the authorization request. The response includes ticket
as expected. An authorization server is to store the value of ticket
into the user’s login session, and attempt to authenticate the user and obtain consent.
Once completed, the server will make a request to Authlete’s /auth/authorization/issue
API to generate an authorization response.
In this document, the authorization request shown in the previous section included response_type=code id_token
. So Authlete is expected to generate an authorization response that includes an authorization code code
and an ID token id_token
in fragment.
Let’s assume the auhtorization server authenticates the user, obtains consent and determines that the user’s unique identifier (subject
) is testuser01
. The server will be making a request to Authlete’s /auth/authorization/issue
API with the ticket
and the subject
. Here’s a curl version of the request.
curl -s -X POST https://api.authlete.com/api/auth/authorization/issue \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"subject":"testuser01","ticket":"rjasCNvemUwamKP0G1h9Fh5Uo_3fgBfQsTF1PU4-GiE"}' | jq
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization/issue `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"subject\":\"testuser01\",\"ticket\":\"rjasCNvemUwamKP0G1h9Fh5Uo_3fgBfQsTF1PU4-GiE\"}'
Authlete makes the following response (folded for readability).
{
"type": "authorizationIssueResponse",
"resultCode": "A151301",
"resultMessage": "[A151301] The algorithm
('HS256' for 'id_token_signed_response_alg')
to sign the ID token is not allowed.",
[...]
According to the value of resultMessage
, Authlete doesn’t allow HS256 (Authlete’s default settings) as a signing algorithm for ID token.
This is due to security provisions of FAPI. 8.6. Algorithm considerations states as follows.
- shall use PS256 or ES256 algorithms;
Thus a client and an authorization server have to employ either ES256 or PS256 as JWS algorithm. In this document, we will be configuring Authlete to use ES256 for ID token signing algorithm.
Now we are going to generate an ES256 key set and register it to Authlete as a signing key for ID token. We will be also updating the client settings to specify ES256 as a signing algorithm for the client. See the following article in Authlete Knowledge Base for instructions.
Parameters for mkjwk are as follows.
Item | Value |
---|---|
Tab | EC (Elliptic Curve) |
Curve | P-256 |
Key Use | Signing |
Algorithm | ES256 (ECDSA using P-256 and SHA-256) |
Key ID | An arbitrary value e.g. 1 |
Register the generated “Keypair set” to the service by logging into Authlete’s Service Owner Console https://so.authlete.com/
, adding the keypair set to “JWK Set Content” section in JWK Set tab, and enter a value of kid of the keypair set, 1
in this example, to “ID Token Signature Key ID” section in the same page.
Then log into Developer Console for the service (https://cd.authlete.com/<API Key>
e.g. https://cd.authlete.com/174381609020
), click a link to the client, click “Edit” button in the bottom of the page to make settings editable, and go to ID Token tab.
There should be ID Token Signature Algorithm section.
Choose ES256 from dropdown list and click “Update” button.
Those configuration will have Authlete select ES256 as a signing algorithm for issuing ID token for this client and use the registered ES256 key.
Let’s check if Authlete works as expected. Make the same request again to /auth/authorization
API to get a ticket
at first.
curl -s -X POST https://api.authlete.com/api/auth/authorization \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj&request=<Request Object e.g. eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoiY2xhaW1zIjp7CiAgImlkX3Rva2VuIjp7CiAgICAiYWNyIjp7CiAgICAgICJlc3NlbnRpYWwiOnRydWUsCiAgICAgICJ2YWx1ZXMiOlsidXJuOmV4YW1wbGU6cHNkMjpzY2EiXQogICAgfQogIH0KfSwKIm5vbmNlIjoibi0wUzZfV3pBMk1qIgp9Cg.b5rDSqaI3dh8n4A8hK4B5zSpnZNO_8--W-kTU03CNbCq1I_Vuf3w33ZVUhD0A-rla8cTPlZ25keQBncGWafzOA>"}' | jq | grep ticket
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"parameters\": \"redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=<Client ID e.g. 591205987816490>&nonce=n-0S6_WzA2Mj&request=<Request Object e.g. eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoiY2xhaW1zIjp7CiAgImlkX3Rva2VuIjp7CiAgICAiYWNyIjp7CiAgICAgICJlc3NlbnRpYWwiOnRydWUsCiAgICAgICJ2YWx1ZXMiOlsidXJuOmV4YW1wbGU6cHNkMjpzY2EiXQogICAgfQogIH0KfSwKIm5vbmNlIjoibi0wUzZfV3pBMk1qIgp9Cg.b5rDSqaI3dh8n4A8hK4B5zSpnZNO_8--W-kTU03CNbCq1I_Vuf3w33ZVUhD0A-rla8cTPlZ25keQBncGWafzOA>\"}'
You should be able to obtain a response (folded for readability) including ticket
.
{
[...]
"resultMessage": "[A004001] Authlete has successfully issued a ticket to the service
(API Key = 174381609020) for the authorization request from the client
(ID = 591205987816490). [response_type=code id_token, openid=true]",
"ticket": "3TzdZO2t8qXaQXIEUA5LLN106uVk5fpwL8_UDGlcwUQ"
[...]
Make a request with the value of the ticket
and an arbitrary value of subject
(testuser01
in this example) to /auth/authorization/issue
API.
curl -s -X POST https://api.authlete.com/api/auth/authorization/issue \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"subject":"testuser01","ticket":"3TzdZO2t8qXaQXIEUA5LLN106uVk5fpwL8_UDGlcwUQ"}' | jq
curl.exe -s -X POST https://api.authlete.com/api/auth/authorization/issue `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"subject\":\"testuser01\",\"ticket\":\"3TzdZO2t8qXaQXIEUA5LLN106uVk5fpwL8_UDGlcwUQ\"}'
Authlete makes the following response (folded for readability).
{
"type": "authorizationIssueResponse",
"resultCode": "A040001",
"resultMessage": "[A040001] The authorization request was processed successfully.",
"accessTokenDuration": 0,
"accessTokenExpiresAt": 0,
"action": "LOCATION",
"authorizationCode": "TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ",
"idToken": "eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJjX2hhc2giOiJZQjloU01CWkJLdnFobnFaWWRWTXJnIiwiaXNzIjoiaHR0cHM6L
y9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMDUyNiwiaWF0IjoxNTcyMzI0MTI2LCJub25jZSI6I
m4tMFM2X1d6QTJNaiJ9.ZY5XK4TqAfcnLsMhkigNRpyM6CvwD7SdX-f9TQ18pwMUdh7eoGc6ijlfEnc4
I3l0jYhlm22yuEeffV6XZhdL0A",
"responseContent": "https://client.example.org/cb/example.com#
code=TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ&
id_token=eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJjX2hhc2giOiJZQjloU01CWkJLdnFobnFaWWRWTXJnIiwiaXNzIjoiaHR0cHM6L
y9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMDUyNiwiaWF0IjoxNTcyMzI0MTI2LCJub25jZSI6I
m4tMFM2X1d6QTJNaiJ9.ZY5XK4TqAfcnLsMhkigNRpyM6CvwD7SdX-f9TQ18pwMUdh7eoGc6ijlfEnc4
I3l0jYhlm22yuEeffV6XZhdL0A"
}
According to the value of resultMessage
, Authlete successfully processed the request. There are an issued ID token and an authorization code in idToken
and authorizationCode
respectively, and HTTP response content in responseContent
. The content includes these token and code as fragment and is intended to be sent from an authorizartion server to a client as an authorization response.
Once the client received the response from the authorization server it will verify the ID token. If the verification is done successfully, it will extract c_hash
from the ID token and use it to verify the authorization code. If the second verification is also done successfully, the client will make a token request with the code to the authorzation server.
The authorization server will make a request including the token request, to Authlete’s /auth/token
API to have the API generate a token response which should include an access token.
Here’s a curl version of the request to /auth/token
API.
curl -s -X POST https://api.authlete.com/api/auth/token \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"clientId":"<Client ID e.g. 591205987816490>","clientSecret":"<Client Secret e7iqzq7WE8Kg00yepYnpMTjvDnAnBlq5nfA9DDQLkiYkPQBV6Lr8sLhn7DhUJd17i0O6TwQ2hKFeDAYuU160Vg>","parameters": "grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=<Code e.g. TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ>"}'|jq
curl.exe -s -X POST https://api.authlete.com/api/auth/token `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"clientId\":\"<Client ID e.g. 591205987816490>\",\"clientSecret\":\"<Client Secret e7iqzq7WE8Kg00yepYnpMTjvDnAnBlq5nfA9DDQLkiYkPQBV6Lr8sLhn7DhUJd17i0O6TwQ2hKFeDAYuU160Vg>\",\"parameters\": \"grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=<Code e.g. TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ>\"}'
Authlete makes the following error response (folded for readability).
{
"type": "tokenResponse",
"resultCode": "A157301",
"resultMessage": "[A157301] The client type of the client is 'confidential'
but the client authentication method is 'none'.",
"accessTokenDuration": 0,
"accessTokenExpiresAt": 0,
"action": "INVALID_CLIENT",
"clientId": 591205987816490,
"clientIdAliasUsed": false,
"grantType": "AUTHORIZATION_CODE",
"refreshTokenDuration": 0,
"refreshTokenExpiresAt": 0,
"responseContent": "{\"error_description\":\"[A157301] The client type
of the client is 'confidential' but the client authentication method is 'none'.\",
\"error\":\"invalid_client\",\"error_uri\":\"https://docs.authlete.com/#A157301\"}"
}
According to the value of resultMessage
, the client authentication method of none
, which is the default value of Authlete, is not allowed for confidential clients.
This is due to security provisions of FAPI. 5.2 Read and write API security provisions / 5.2.2. Authorization server states as follows.
- shall authenticate the confidential client using one of the following methods (this overrides FAPI Security Profile 1.0 - Part 1: Baseline clause 5.2.2-4):
tls_client_auth
orself_signed_tls_client_auth
as specified in section 2 of MTLS, orprivate_key_jwt
as specified in section 9 of OIDC;
Thus you have to use either
Mutual TLS for OAuth Client Authentication defined in RFC 8705 or private_key_jwt defined in OpenID Connect Core 1.0. That is, other methods such as none
, client_secret_basic
that is popular one for confidential clients, are prohibited i.e. an authorization server must not employ them to authenticate clients.
In this document, we will be having Authlete use the former one and PKI Mutual-TLS Method (tls_client_auth
) defined in the method.
private_key_jwt
.
Note that you have to configure mutual TLS to enable "Holder of Key," even if the client authentication method is employed.
Let’s configure both the service and the client settings to enable TLS client authentication.
Log into Authlete’s Service Owner Console https://so.authlete.com/
, click “Edit” button in the bottom of the page to make settings editable, and go to Authorization tab. There should be Token Endpoint section.
Check the box at Supported Client Authentication Methods and click “Update” button in the bottom of the page. Press “OK” in a dialog for confirmation.
Item | Value |
---|---|
Supported Client Authentication Methods | TLS_CLIENT_AUTH |
Log into Developer Console for the service (https://cd.authlete.com/<API Key>
e.g. https://cd.authlete.com/174381609020
) and configure the client’s settings at “Token Endpoint” section in Authorization tab as follows.
Item | Value |
---|---|
Client Authentication Method | TLS_CLIENT_AUTH |
Item | Value |
---|---|
TLS Client Auth Subject DN | CN=client.example.org, O=Client, L=Chiyoda-ku, ST=Tokyo, C=JP |
WIth those settings above, Authlete will support mutual TLS authentication for client authentication and apply the method to process token requests from the client. Subject DN CN=client.example.org, ...
is used as the identifier of the client.
You have to prepare a digital certificate for the client to be authenticated by the authorization server. The subject DN of the certificate must be the same as one that
has been specified in the previous section, CN=client.example.org, ...
in this document.
The following example illustrates that generating a self-signed certificate by using OpenSSL.
$ openssl genrsa 2048 > private_key_nopass.pem
Generating RSA private key, 2048 bit long modulus
...............+++
.......................+++
e is 65537 (0x10001)
CN=client.example.org, O=Client, L=Chiyoda-ku, ST=Tokyo, C=JP
as Subject DN$ openssl req -new -key private_key_nopass.pem -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) []:Chiyoda-ku
Organization Name (eg, company) []:Client
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:client.example.org
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
$ cat server.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRMwEQYD
VQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNVBAMMEmNsaWVu
[...]
r4MUVOwPNWOM6UGYQZwjvtJ2rmKr8cQrbfvbcFiY4s6lLQGOz5yLzmO8GUdmfzUd
p5BW1iL+SpjS
-----END CERTIFICATE REQUEST-----
$ openssl x509 -days 365 -req -signkey private_key_nopass.pem -in server.csr -out server.crt
Signature ok
subject=/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Client/CN=client.example.org
Getting Private key
The following one is a certificate generated in this example.
server.crt
-----BEGIN CERTIFICATE-----
MIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK
UDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM
BkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3
MjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv
a3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV
BAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF
XrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9
J3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL
msYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq
df6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj
mx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
qzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM
z3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9
R6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf
mAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv
lmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr
So6zun26vAUJTu1o9CIjxw==
-----END CERTIFICATE-----
The certificate gets transformed into one line as follows, so that it can be used in requests with curl command.
-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n
Let’s check if Authlete works as expected. Run the same procedure again.
/auth/authorization
API and obtain a value of ticket
from a response/auth/authorization/issue
API and obtain a value of authorizationCode
from a response/auth/token
API
* Add clientCertificate
parameter with the client certificate as its value
* Remove clientSecret
parameter (it is no longer required as mutual TLS authentication is effective)Here’s a curl version of the request to /auth/token
API.
curl -s -X POST https://api.authlete.com/api/auth/token \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"clientId":"<Client ID e.g. 591205987816490>","parameters": "grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=<Code e.g. TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ>", "clientCertificate":"-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n"}'|jq
curl.exe -s -X POST https://api.authlete.com/api/auth/token `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"clientId\":\"<Client ID e.g. 591205987816490>\",\"clientSecret\":\"<Client Secret e7iqzq7WE8Kg00yepYnpMTjvDnAnBlq5nfA9DDQLkiYkPQBV6Lr8sLhn7DhUJd17i0O6TwQ2hKFeDAYuU160Vg>\",\"parameters\": \"grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=<Code e.g. TSRAvPIp6V3RgPOs2O7FpPG1_7t6Xpc_kcIramz8gBQ>\",\"clientCertificate\":\"-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n\"}'
Authlete makes the following response with caution (folded for readability).
{
"type": "tokenResponse",
"resultCode": "A152305",
"resultMessage": "[A152305] The service and the client are not configured
so that the required Holder of Key methods are performed.",
"accessToken": "RCqjF4tlffJ7-n92sAEFQNIwrRm0syOUrBu0cNLAIJU",
"accessTokenDuration": 0,
"accessTokenExpiresAt": 0,
"action": "BAD_REQUEST",
"clientId": 591205987816490,
"clientIdAliasUsed": false,
"grantType": "AUTHORIZATION_CODE",
"idToken": "eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJpc3MiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiZXhwIjoxNTcyNDEyMTcwL
CJpYXQiOjE1NzIzMjU3NzAsIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.x4XmPTh698AbNEjCaNcD5k54q
S249BSPkc9EkwZuUI17AL8z593GYTg3GVQQdhF9k0HYLRA17c3m39OxYDrx3g",
"refreshToken": "jy5lN7TXZrAlIfgFbOaMMkzDtoJUu4prBtNa3HcoRRE",
"refreshTokenDuration": 0,
"refreshTokenExpiresAt": 0,
"responseContent": "{\"error_description\":\"[A152305] The service and the client are
not configured so that the required Holder of Key methods are performed.\",
\"error\":\"invalid_request\",\"error_uri\":\"https://docs.authlete.com/#A152305\"}"
}
According to the value of resultMessage
, Authlete didn’t perform “Holder of Key” method, which is mandatory to comply with the security provisions of FAPI, while an access token has been issued.
Thus the issued access token is not bound with the TLS client certificate of the client.
Log into Authlete’s Service Owner Console https://so.authlete.com/
, click “Edit” button in the bottom of the page to make settings editable, and go to Token tab. There should be Access Token section.
Choose the following option for TLS Client Certificate Bound Access Tokens.
Item | Value |
---|---|
TLS Client Certificate Bound Access Tokens | Choose Supported |
Log into Developer Console for the service (https://cd.authlete.com/<API Key>
e.g. https://cd.authlete.com/174381609020
), click a link to the client, click “Edit” button in the bottom of the page to make settings editable, and go to Basic tab.
Choose the following option for TLS Client Certificate Bound Access Tokens.
Item | Value |
---|---|
TLS Client Certificate Bound Access Tokens | Choose Enabled |
Congraturations! Finally you have finished configuration of Authlete to support a FAPI-compliant authorization server.
Once the configuration is done you are able to check if Authlete works as expected.
Make requests, which are the same as the ones in the previous section,
to /auth/authorization
API, /auth/authorization/issue
API and /auth/token
API.
In addition to these three requests, make another request to /auth/introspection
API to see if the access token used for API requests to a resource server is bound with the TLS client certificate of the client.
/auth/authorization
APIcurl -s -X POST https://api.authlete.com/api/auth/authorization -u '174381609020:LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k' -H 'Content-Type: application/json' -d '{"parameters": "redirect_uri=https://client.example.org/cb/example.com&scope=openid+payment&response_type=code+id_token&client_id=591205987816490&nonce=n-0S6_WzA2Mj&request=eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.ewoicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9jbGllbnQuZXhhbXBsZS5vcmcvY2IvZXhhbXBsZS5jb20iLAoicmVzcG9uc2VfdHlwZSI6ImNvZGUgaWRfdG9rZW4iLAoiY2xpZW50X2lkIjoiNTkxMjA1OTg3ODE2NDkwIiwKInNjb3BlIjoib3BlbmlkIHBheW1lbnQiLAoiZXhwIjoxNTU0OTczMDAwMCwKImF1ZCI6Imh0dHBzOi8vYXMuZXhhbXBsZS5jb20iLAoiY2xhaW1zIjp7CiAgImlkX3Rva2VuIjp7CiAgICAiYWNyIjp7CiAgICAgICJlc3NlbnRpYWwiOnRydWUsCiAgICAgICJ2YWx1ZXMiOlsidXJuOm1hY2U6aW5jb21tb246aWFwOnNpbHZlciJdCiAgICB9CiAgfQp9LAoibm9uY2UiOiJuLTBTNl9XekEyTWoiCn0K.50gewunAqCITD6p2kI52GDXdUgQP-EzLDjjjoDT9C4zY8YCKgLzN7sR2ZvkAQ_pimLpwFh2QYjjyPskvvtnC9g"}' |jq|grep ticket
{
[...]
"resultMessage": "[A004001] Authlete has successfully issued a ticket
to the service (API Key = 174381609020) for the authorization request
from the client (ID = 591205987816490). [response_type=code id_token, openid=true]",
[...]
"ticket": "b0JGD-ZkT8ElBGw2ck-T-t87Z033jXvhqC2omPT1bQ4"
[...]
/auth/authorization/issue
APIcurl -s -X POST https://api.authlete.com/api/auth/authorization/issue -u '174381609020:LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k' -H 'Content-Type: application/json' -d '{"subject":"testuser01","ticket":"b0JGD-ZkT8ElBGw2ck-T-t87Z033jXvhqC2omPT1bQ4"}' | jq
{
"type": "authorizationIssueResponse",
"resultCode": "A040001",
"resultMessage": "[A040001] The authorization request was processed successfully.",
"accessTokenDuration": 0,
"accessTokenExpiresAt": 0,
"action": "LOCATION",
"authorizationCode": "DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70",
"idToken": "eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJjX2hhc2giOiJqR2kyOElvYm5HcjNNQ3Y0UUVQRTNnIiwiaXNzIjoiaHR0cHM6L
y9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMjY4MiwiaWF0IjoxNTcyMzI2MjgyLCJub25jZSI6I
m4tMFM2X1d6QTJNaiJ9.1PFmc0gAsBWtLBriq3z9a4Tsi_ioEYlOqOYbicGEXWIS1WGX5ffGOyZNSzVB
MamZbltZmSys0jlYmmYYLqgGsg",
"responseContent": "https://client.example.org/cb/example.com#
code=DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70&
id_token=eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJjX2hhc2giOiJqR2kyOElvYm5HcjNNQ3Y0UUVQRTNnIiwiaXNzIjoiaHR0cHM6L
y9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMjY4MiwiaWF0IjoxNTcyMzI2MjgyLCJub25jZSI6I
m4tMFM2X1d6QTJNaiJ9.1PFmc0gAsBWtLBriq3z9a4Tsi_ioEYlOqOYbicGEXWIS1WGX5ffGOyZNSzVB
MamZbltZmSys0jlYmmYYLqgGsg"
}
/auth/token
APIcurl -s -X POST https://api.authlete.com/api/auth/token \
-u '174381609020:LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k' \
-H 'Content-Type: application/json' \
-d '{"clientId":"591205987816490","parameters": "grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70","clientCertificate":"-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n"}' |jq
{
"type": "tokenResponse",
"resultCode": "A050001",
"resultMessage": "[A050001] The token request (grant_type=authorization_code)
was processed successfully.",
"accessToken": "SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU",
"accessTokenDuration": 86400,
"accessTokenExpiresAt": 1572412769390,
"action": "OK",
"clientId": 591205987816490,
"clientIdAliasUsed": false,
"grantType": "AUTHORIZATION_CODE",
"idToken": "eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJpc3MiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiZXhwIjoxNTcyNDEyNzY5L
CJpYXQiOjE1NzIzMjYzNjksIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.9EQojck-Cf2hnKAZWR164kr21
o5lPKehvIHyViZgRg4CY_ZGmnyFooG4FCwlZxu-QOTtaDCffCsuCdz4GqknTA",
"refreshToken": "tXZjYfoK35I-djg9V3n6s58zsrVqRIzTNMXKIS_wkj8",
"refreshTokenDuration": 864000,
"refreshTokenExpiresAt": 1573190369390,
"responseContent": "{\"access_token\":\"SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU\",
\"refresh_token\":\"tXZjYfoK35I-djg9V3n6s58zsrVqRIzTNMXKIS_wkj8\",\"scope\":\"openid payment\",
\"id_token\":\"eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN
Tk4NzgxNjQ5MCJdLCJpc3MiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiZXhwIjoxNTcyNDEyNzY5L
CJpYXQiOjE1NzIzMjYzNjksIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.9EQojck-Cf2hnKAZWR164kr21
o5lPKehvIHyViZgRg4CY_ZGmnyFooG4FCwlZxu-QOTtaDCffCsuCdz4GqknTA\",
\"token_type\":\"Bearer\",\"expires_in\":86400}",
"scopes": [
"openid",
"payment"
],
"subject": "testuser01"
}
After the procedure above, we have got an access token whose value is SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU
in this example.
Let’s assume that the resource server receives an API request, including
the value as the access token, from the client. The token in the request would be in Authorization: Bearer
header.
The resource server is to
verify the token and obtain related information with it, by making a
request to /auth/introspection
API. The resource server will also include
the client certificate, which should be able to obtained from mutual TLS
communication for the API request, into the request to Authlete.
/auth/introspection
APIcurl -s -X POST https://api.authlete.com/api/auth/introspection \
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' \
-H 'Content-Type: application/json' \
-d '{"token":"SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU","clientCertificate":"-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n"}'|jq
curl.exe -s -X POST https://api.authlete.com/api/auth/introspection `
-u '<API Key e.g. 174381609020>:<API Secret e.g. LszYEVDLM5Bu4lRjO9Vaj0tMSMVerWiPf_zcdy-vu4k>' `
-H 'Content-Type: application/json' `
-d '{\"token\":\"SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU\",\"clientCertificate\":\"-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n\"}'
{
"type": "introspectionResponse",
"resultCode": "A056001",
"resultMessage": "[A056001] The access token is valid.",
"action": "OK",
"certificateThumbprint": "cBNP0zNH0fkcIQdVHdB8GDQAbaZyIjKXB0EVRTByJMU",
"clientId": 591205987816490,
"clientIdAliasUsed": false,
"existent": true,
"expiresAt": 1572412769000,
"refreshable": true,
"responseContent": "Bearer error=\"invalid_request\"",
"scopes": [
"openid",
"payment"
],
"subject": "testuser01",
"sufficient": true,
"usable": true
}
The resource server is now able to find that the access token from the client has been verified and get the associated information with the token such as subject and scopes.
In this tutorial, we reviewed security provisions defined in Financial-grade API Security Profile 1.0 - Part 2: Advanced and configuration instructions of Authlete through steps for building a FAPI compliant authorization server.