3. Token Exchange

Token exchange refers to the process of making a POST request to the token endpoint to exchange the authorization code (obtained from the redirect URL in the previous step) with an ID token and access token.

The token endpoint is defined in the token_endpoint field located in our authorization server's OpenID Configuration.

The process of token exchange involves two parts:

  1. Generation of a client assertion, which we will use to authenticate you.

  2. Sending the client assertion together with the request body to the token endpoint.

Generation of client assertion

If you are using a certified OIDC Relying Party library, the generation of client assertion will be handled by the library, as long as you specify that the private_key_jwt method is used for authentication.

You will need to generate a client assertion using private_key_jwt mechanism specified in the OIDC specifications. This mechanism involves you building a client assertion by signing a JWT using one of your signing keys defined in your JWKS.

To reduce complexity, we recommend that you use a JWT library to perform the JWT encoding and signing on your behalf, instead of implementing this on your own. You may refer to this list to look for a suitable library for your programming language.

The JWT must have the structure outlined below.

JWT Header

The JWT header should contain the following parameters:

Parameter
Description
Data Type

alg

The signature algorithm that you are using to sign this JWT

One of the following strings:

  • ES256

  • ES384

  • ES512

typ

The type of this JWT

Must be the string JWT

kid

The kid of the signing key that you are using to sign this JWT header. If this is not provided, we will test against all of the signing keys in your JWKS when attempting to verify the signature.

String, optional

Sample JWT header
{
  "typ" : "JWT",
  "alg" : "ES256",
  "kid" : "my_key_id_01"
}

JWT Payload

The JWT payload should contain the following claims:

Claim
Description
Data type

sub

The client ID of your registered client, provided by Singpass during app onboarding.

A 32-character case-sensitive alphanumeric string.

aud

This should be the issuer identifier of our authorization server. You can obtain this value from the issuer field in the OpenID configuration of our authorization server.

String

iss

The client ID of your registered client, provided by Singpass during app onboarding.

A 32-character case-sensitive alphanumeric string.

iat

The unix timestamp, in seconds, at which you generated this JWT.

Number

exp

The unix timestamp, in seconds, on or after which this JWT must not be accepted by us for processing. Note also that this must be less than or equal to 2 minutes after iat.

Number

jti

A unique identifier for this token. This identifier must only be used once. You should generate a new jti value for every request

String

code

The authorization code issued by us, obtained from the redirect URL in the previous step.

A base64url-encoded string.

Sample JWT Payload
{
  "sub": "T5sM5a53Yaw3URyDEv2y9129CbElCN2F",
  "aud": "https://id.singpass.gov.sg/fapi",
  "iss": "T5sM5a53Yaw3URyDEv2y9129CbElCN2F",
  "iat": "1756785377",
  "exp": "1756785493",
  "code": "XcyzlSeX1hIyJFlstxsSF_UeXC5DtiYkFgJ8VVx52mg"
}
Sample Signed JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjY3NTUxZWE2ZWE2NDk2ZmZiMWRkZmI4YTFhM2FlYjA0In0.eyJzdWIiOiJUNXNNNWE1M1lhdzNVUnlERXYyeTkxMjlDYkVsQ04yRiIsImF1ZCI6Imh0dHBzOi8vaWQuc2luZ3Bhc3MuZ292LnNnL2ZhcGkiLCJpc3MiOiJUNXNNNWE1M1lhdzNVUnlERXYyeTkxMjlDYkVsQ04yRiIsImlhdCI6IjE3NTY3ODUzNzciLCJleHAiOiIxNzU2Nzg1NDkzIiwiY29kZSI6IlhjeXpsU2VYMWhJeUpGbHN0eHNTRl9VZVhDNUR0aVlrRmdKOFZWeDUybWcifQ.fQMHTVNeCsa_mK6pmfjfi0rxERxDtu6eiMFkkpFdQrirc3u9LxP1_-E_7yIalIhmot1NdBQxbA9s_VztlQUhTQ

Request Body

You should send the request body in application/x-www-form-urlencoded format. The DPoP header containing the DPoP JWT proof should also be included in your request.

The request body should have the following parameters:

Parameter
Description
Data type

client_id

The client ID of your registered client, provided by Singpass during app onboarding.

A 32-character case-sensitive alphanumeric string.

redirect_uri

The redirect_uri which you had used earlier when making the Authorization request.

URL

grant_type

The grant being requested.

Must be the string authorization_code, as mandated by OIDC specifications.

code

The authorization code issued by us, obtained via redirection back to your redirect_url in the previous step.

A base64url-encoded string.

client_assertion_type

The type of client assertion.

Must be the string urn:ietf:params:oauth:client-assertion-type:jwt-bearer, as mandated by OIDC specifications.

client_assertion

Your client assertion, generated using the instructions in the "Authentication" section above.

A string containing the signed JWT.

code_verifier

The code verifier which you have generated when constructing your authorization request.

String containing only alphanumeric characters, dashes, and underscores, between 43-128 characters long.

Sample request
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiQXNWakh4elZ4MURaNnNKcEpzUnM2ek5YYXFmcFR3UmNfcXV0bWw0aEFJQSIsInkiOiI0SkhwVVZDRE5DaXhOTW9OclIzSElodFRzTWNfMF9NcmdpMzJxR3VoUkQ4In19.eyJqdGkiOiIyZmM3Y2Q4ZC0xN2IzLTRlYTUtYTg4ZC1lZWM0NTY5M2JjZmUiLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vZGF0YSIsImlhdCI6MTc1NjcwODI0N30.wBYjFQRzY2o5aG82LjR1X8qT9bZ-jK3c7hI5fE0gP9vR_7sU6tW5xV4yZ3aB2cE1dF0eG9hI8jJ7kL6mN5oP4qR
Host: id.singpass.gov.sg

grant_type=authorization_code&redirect_uri=https%3A%2F%2Fpartner.gov.sg%2Fredirect&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjY3NTUxZWE2ZWE2NDk2ZmZiMWRkZmI4YTFhM2FlYjA0In0.eyJzdWIiOiJUNXNNNWE1M1lhdzNVUnlERXYyeTkxMjlDYkVsQ04yRiIsImF1ZCI6Imh0dHBzOi8vaWQuc2luZ3Bhc3MuZ292LnNnL2ZhcGkiLCJpc3MiOiJUNXNNNWE1M1lhdzNVUnlERXYyeTkxMjlDYkVsQ04yRiIsImlhdCI6IjE3NTY3ODUzNzciLCJleHAiOiIxNzU2Nzg1NDkzIiwiY29kZSI6IlhjeXpsU2VYMWhJeUpGbHN0eHNTRl9VZVhDNUR0aVlrRmdKOFZWeDUybWcifQ.fQMHTVNeCsa_mK6pmfjfi0rxERxDtu6eiMFkkpFdQrirc3u9LxP1_-E_7yIalIhmot1NdBQxbA9s_VztlQUhTQ&client_id=T5sM5a53Yaw3URyDEv2y9129CbElCN2F&code_verifier=mN7szCmmIc6Z2Vg-iaX7f7RDVsKAhY5GG-r7Crq0jxTdxY0xyPKnsAEWtEMdZ3D8QW5rs-C824W3Jwntcw

Response Body

We will return a response in application/json format. This response includes the following parameters:

Parameter
Description
Data type

access_token

For Login applications, this is a random string which should not be used. For Myinfo (v5) applications, this is an encoded JWT that can be used to obtain user information using the UserInfo endpoint. This access token has a lifetime of 30 minutes.

String

id_token

The encrypted ID token in JWT format, signed by us. More information on how to parse this ID token is available in the next page.

String

token_type

The type of token being requested.

This will always be DPoP, as mandated by FAPI 2.0 specifications.

Sample response body
{
  "id_token": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.A3-aQ1V0a2-FFgT9YS8b_vX-k8aPyiQh_C0d7gB5rE8e_g6hI5jK4lM3nO2pQ1rS_tU9vW8xY7zZ-aB1c2D3eF4gH5iJ6kL7mN8oP9qR0sT1uV2wX3yZ4aB5c6D7eF8gH9iJ0kL1mN2oP3qR4sT5uV6wX7yZ8aB9c.3f8v2n5k9b1m4p7s.g-sW3r_x9zY7vU5tQ3rS-p1oN0mK9jI7hG5fD3c2B1a_Z_Y-X_W_V_U_T_S_R_Q_P_O_N_M_L_K_J_I_H_G_F_E_D_C_B_A-placeholder-ciphertext.V2xX1yZ0aB1c2D3eF4gH5iJ6kL7",
  "access_token": "Ul5JqK8vJX8Jfgo7UjpiIigF7DciW7YGnUACLN1/T80=",
  "token_type": "DPoP"
}

Last updated

Was this helpful?