Financial-grade API (FAPI) Basics

Preface

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.

Components

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).

Components in this tutorial

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

Interaction between API client and API server using FAPI

In this document, you will configure Authlete to enable the following token granting process and API access flows in a FAPI-compliant manner.

FAPI-compliant token granting process and API access flows

1. Authorization request

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.

Other settings, including claims, are to be explained later.

2. Authorization response

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.

3. Token request

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.

4. Token response

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.

5. API request

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.

Initial setup

Adding a new Authlete service with a client

Adding a new Authlete service and enabling FAPI support

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.

Supported Service Profiles in Service Details

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.

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

Create Scope in Service Owner Console

Adding a new client and basic settings

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

Testing the initial configuration

/auth/authorization API

Let’s check how Authlete works with the initial configuration. We will use /auth/authorization API for that purpose.

Authorization request for non-FAPI scope

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.

Authorization request for FAPI scope (Part 1)

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
    1. the response_type value code id_token, or
    2. the response_type value code in conjunction with the response_mode value jwt;

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.

Authorization request for FAPI scope (Part 2)

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 or request_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.

Making a FAPI-compliant authorization request

In this section, we will create an authorization request using a a request object.

Request object settings

Configuring a signing key used for 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

Generating a JWK Set with mkjwk

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"
    }
  ]
}

Additional client settings

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.

  • JWK Set tab
Item Value
JWK Set Content Paste content of es256_keyset_pub.txt

JWK Set Content in Developer Console

  • Authorization tab
Item Value
Request Object Signature Algorithm ES256

Request Object Signature Algorithm in Developer Console

Now we have finished preparation to have Authlete verify a signature of a request object.

Creating 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": &lt;Unix time of the current time&gt; e.g. 1613373232,
"exp": &lt;nbf + 3600&gt; e.g. 1613376832,
"exp":15549730000,
"aud":"https://as.example.com",
"nonce":"n-0S6_WzA2Mj"
}

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

Generating a signed JWT with mkjose

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

Testing the configuration

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.

/auth/authorization/issue API

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.

Making a FAPI-compliant authorization response

Changing ID token signing algorithm

Adding a signing key for ID token

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.

JWK Set Content and ID Token Signature Key ID in Service Owner Console

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.

ID Token Signature Algorithm in Developer Console

Testing the configuration (ES256 for ID token)

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.

/auth/token API

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):
    1. tls_client_auth or self_signed_tls_client_auth as specified in section 2 of MTLS, or
    2. private_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.

Making a FAPI-compliant token request

TLS client authentication configuration

Let’s configure both the service and the client settings to enable TLS client authentication.

Service settings for TLS client authentication configuration

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

Supported Client Authentication Methods in Service Owner Console

Client settings for TLS client authentication configuration

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

Client Authentication Method in Developer Console

Item Value
TLS Client Auth Subject DN CN=client.example.org, O=Client, L=Chiyoda-ku, ST=Tokyo, C=JP

TLS Client Auth Subject DN in Developer Console

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.

Generating a self-signed certificate

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.

  • Generating a private key
$ openssl genrsa 2048  > private_key_nopass.pem
Generating RSA private key, 2048 bit long modulus
...............+++
.......................+++
e is 65537 (0x10001)
  • Generating a CSR
    • Using 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-----
  • Generating a certificate
$ 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

Testing the configuration (Mutual TLS)

Let’s check if Authlete works as expected. Run the same procedure again.

  1. Make a request to /auth/authorization API and obtain a value of ticket from a response
  2. Make a request to /auth/authorization/issue API and obtain a value of authorizationCode from a response
  3. Make a request with the following modification to /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.

Making a FAPI-compliant token response

Holder of Key configuration

Service settings for access token configuration

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

TLS Client Certificate Bound Access Tokens in Service Owner Console

Client settings for access token configuration

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

TLS Client Certificate Bound Access Tokens in Developer Console

Congraturations! Finally you have finished configuration of Authlete to support a FAPI-compliant authorization server.

A complete example walk through

FAPI-compliant token granting process and API access flows using Authlete

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.

Authorization request

curl -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
  • Response (folded for readability)
{
[...]
  "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"
  [...]

Authorization response

curl -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
  • Response (folded for readability)
{
  "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"
}

Token request and token response

curl -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
  • Response (folded for readability)
{
  "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"
}

API request

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 API

curl -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\"}'
  • Response (folded for readability)
{
  "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.

Conclusion

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.