# Authorization Code Grant

{% hint style="danger" %}
All Login and Myinfo apps must follow Singpass' [FAPI 2.0-compliant authentication API](https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide) by 31 Dec 2026.

The specifications on this page apply to you only if you are maintaining an existing Login / Myinfo (v5) integration. We encourage you to [migrate](https://docs.developer.singpass.gov.sg/docs/technical-specifications/migration-guides/login-myinfo-v5-apps) early to avoid service disruptions.
{% endhint %}

## Client Authentication - Client Assertion JWT

### Assertion JWT Structure

The RP is required to generate an assertion JWT that has the following header and claims, and is signed with the JWK that was provided during onboarding.

#### **JWT Header**

The header **must include `alg` and `typ`**.

The supported `alg` types are:

* `ES256`
* `ES384`
* `ES512`.

This must match the `alg` value in the signing key used to sign the assertion (if the signing JWK specifies `alg` explicitly).

The header should also include `kid` of the signing key to help identify which of the RP’s signing keys was used, though this is not mandatory. If omitted, we will test against all known signing keys when attempting to verify the signature.

*example*

```json
{
  "typ": "JWT",
  "alg": "ES256",
  "kid": "rp_key_01"
}
```

#### **JWT Claims**

| Path   | Type     | Description                                                                                                                                                                                                                                                                                                  |
| ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sub`  | `String` | This should be `client_id` of the registered client.                                                                                                                                                                                                                                                         |
| `aud`  | `String` | The recipient that the JWT is intended for. This must match the `issuer` field in the response of the OpenID discovery endpoint (<https://id.singpass.gov.sg/.well-known/openid-configuration>) e.g. [https://id.singpass.gov.sg](https://id.singpass.gov.sg/).                                              |
| `iss`  | `String` | This should be `client_id` of the registered client.                                                                                                                                                                                                                                                         |
| `iat`  | `Number` | The time at which the JWT was issued. <https://tools.ietf.org/html/rfc7519#section-4.1.6>                                                                                                                                                                                                                    |
| `exp`  | `Number` | The expiration time on or after which the JWT MUST NOT be accepted by Singpass for processing. Additionally, Singpass will not accept tokens with an `exp` longer than 2 minutes since `iat`. <https://tools.ietf.org/html/rfc7519#section-4.1.4>                                                            |
| `jti`  | `String` | A unique identifier for this token. This identifier must only be used once. You should generate a new `jti` value for every request.                                                                                                                                                                         |
| `code` | `String` | (Optional) This should be the auth `code` which is used to exchange for an ID token. It should be identical to the `code` form param sent outside the `client_assertion`. This enables increased security by signing the `code` so that the `client_assertion` can only be used once for a specific request. |

### Request / Response Structure

Curl request

```bash
$ curl 'https://stg-id.singpass.gov.sg/token' -i -X POST \
    -H 'Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1' \
    -d 'grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJraWQiOiJycF9rZXlfMDEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiJnblk2RXJpY2hwYjV0NE5GUlA5UjRMN2FFQzlOMEZRSCIsImF1ZCI6Imh0dHBzOi8vc3RnLWlkLnNpbmdwYXNzLmdvdi5zZyIsImNvZGUiOiJuMGVzYzNOUnplN0xUQ3U3aVl6UzZhNWFjYzNmMG9ncDQiLCJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJleHAiOjE3MjczMjIwNjUsImlhdCI6MTcyNzMyMTk0NX0.AAAAAAAAAAAAAAAAAAAAAIvHG8uapa4w10pPn_S_7uyYPXNJWU7389sTLNHXrCKhAAAAAAAAAAAAAAAAAAAAAIxyKbbZfLSkduU6WIPrrPe1kGvmHmzXL4PHpgAJ0g0d&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&client_id=gnY6Erichpb5t4NFRP9R4L7aEC9N0FQH&code_verifier=mN7szCmmIc6Z2Vg-iaX7f7RDVsKAhY5GG-r7Crq0jxTdxY0xyPKnsAEWtEMdZ3D8QW5rs-C824W3Jwntcw'
```

#### Form parameters

<table><thead><tr><th width="278">Parameter</th><th>Description</th></tr></thead><tbody><tr><td><code>client_id</code></td><td>The Client Identifier registered. It is the App ID found at the top of your app configuration page.</td></tr><tr><td><code>redirect_uri</code></td><td>The redirect URI being used in this auth session.</td></tr><tr><td><code>grant_type</code></td><td>The type of grant being requested. This must be set to <code>authorization_code</code>.</td></tr><tr><td><code>code</code></td><td>The code issued earlier in the auth session.</td></tr><tr><td><code>scope</code></td><td>(Optional) If no value is provided, it defaults to <code>openid</code>. If provided, then only <code>openid</code> is allowed.</td></tr><tr><td><code>client_assertion_type</code></td><td>This MUST be set to <code>urn:ietf:params:oauth:client-assertion-type:jwt-bearer</code>.</td></tr><tr><td><code>client_assertion</code></td><td>A JWT identifying the client.</td></tr><tr><td><code>code_verifier</code></td><td><p>(Mandatory) This is the session-based, unique, and non-guessable value that the RP had used to generate the <code>code_challenge</code>.</p><p>Must match <code>regexp</code> pattern of <code>[a-zA-Z0-9_\-.~]+</code> minimum length of 43 characters and a maximum length of 128 characters.</p></td></tr></tbody></table>

#### HTTP request

```http
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1
Host: stg-id.singpass.gov.sg
Content-Length: 788

grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJraWQiOiJycF9rZXlfMDEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiJnblk2RXJpY2hwYjV0NE5GUlA5UjRMN2FFQzlOMEZRSCIsImF1ZCI6Imh0dHBzOi8vc3RnLWlkLnNpbmdwYXNzLmdvdi5zZyIsImNvZGUiOiJuMGVzYzNOUnplN0xUQ3U3aVl6UzZhNWFjYzNmMG9ncDQiLCJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJleHAiOjE3MjczMjIwNjUsImlhdCI6MTcyNzMyMTk0NX0.AAAAAAAAAAAAAAAAAAAAAIvHG8uapa4w10pPn_S_7uyYPXNJWU7389sTLNHXrCKhAAAAAAAAAAAAAAAAAAAAAIxyKbbZfLSkduU6WIPrrPe1kGvmHmzXL4PHpgAJ0g0d&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&client_id=gnY6Erichpb5t4NFRP9R4L7aEC9N0FQH&code_verifier=mN7szCmmIc6Z2Vg-iaX7f7RDVsKAhY5GG-r7Crq0jxTdxY0xyPKnsAEWtEMdZ3D8QW5rs-C824W3Jwntcw
```

#### HTTP response

```http
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
x-ndi-response-category: client-assertion-auth-code
X-XSS-Protection: 0
X-Frame-Options: DENY
Date: Thu, 26 Sep 2024 03:39:05 GMT
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked
Content-Type: application/json
Content-Length: 597

{
  "access_token" : "Ul5JqK8vJX8Jfgo7UjpiIigF7DciW7YGnUACLN1/T80=",
  "token_type" : "Bearer",
  "id_token" : "eyJraWQiOiJuZGlfc3RnXzAxIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJnblk2RXJpY2hwYjV0NE5GUlA5UjRMN2FFQzlOMEZRSCIsInN1YiI6InM9Uzg4MjkzMTRCLHU9MWMwY2VlMzgtM2E4Zi00ZjhhLTgzYmMtN2EwZTRjNTlkNmE5IiwiYW1yIjpbInB3ZCIsInN3ayJdLCJpc3MiOiJodHRwczovL3N0Zy1pZC5zaW5ncGFzcy5nb3Yuc2ciLCJleHAiOjE3MjczMjI1NDUsImlhdCI6MTcyNzMyMTk0NSwibm9uY2UiOiJMNW5tUWZjZXREREllaW5jb3F2Q3JGeUd2K25Ib2JrdjRYb2NOWVBDWGFRPSJ9.bD1osnkuL-hXCXY9L_LbveANix_tBsjk8872bL04tTPFGMkzMjE85W8fTwQXrPSFnwQtgzmLB2__i0vdGYnfgA"
}
```

#### Request body

```http
grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJraWQiOiJycF9rZXlfMDEiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiJnblk2RXJpY2hwYjV0NE5GUlA5UjRMN2FFQzlOMEZRSCIsImF1ZCI6Imh0dHBzOi8vc3RnLWlkLnNpbmdwYXNzLmdvdi5zZyIsImNvZGUiOiJuMGVzYzNOUnplN0xUQ3U3aVl6UzZhNWFjYzNmMG9ncDQiLCJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJleHAiOjE3MjczMjIwNjUsImlhdCI6MTcyNzMyMTk0NX0.AAAAAAAAAAAAAAAAAAAAAIvHG8uapa4w10pPn_S_7uyYPXNJWU7389sTLNHXrCKhAAAAAAAAAAAAAAAAAAAAAIxyKbbZfLSkduU6WIPrrPe1kGvmHmzXL4PHpgAJ0g0d&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&client_id=gnY6Erichpb5t4NFRP9R4L7aEC9N0FQH&code_verifier=mN7szCmmIc6Z2Vg-iaX7f7RDVsKAhY5GG-r7Crq0jxTdxY0xyPKnsAEWtEMdZ3D8QW5rs-C824W3Jwntcw
```

#### Response body

```json
{
  "access_token": "Ul5JqK8vJX8Jfgo7UjpiIigF7DciW7YGnUACLN1/T80=",
  "token_type": "Bearer",
  "id_token": "eyJraWQiOiJuZGlfc3RnXzAxIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJnblk2RXJpY2hwYjV0NE5GUlA5UjRMN2FFQzlOMEZRSCIsInN1YiI6InM9Uzg4MjkzMTRCLHU9MWMwY2VlMzgtM2E4Zi00ZjhhLTgzYmMtN2EwZTRjNTlkNmE5IiwiYW1yIjpbInB3ZCIsInN3ayJdLCJpc3MiOiJodHRwczovL3N0Zy1pZC5zaW5ncGFzcy5nb3Yuc2ciLCJleHAiOjE3MjczMjI1NDUsImlhdCI6MTcyNzMyMTk0NSwibm9uY2UiOiJMNW5tUWZjZXREREllaW5jb3F2Q3JGeUd2K25Ib2JrdjRYb2NOWVBDWGFRPSJ9.bD1osnkuL-hXCXY9L_LbveANix_tBsjk8872bL04tTPFGMkzMjE85W8fTwQXrPSFnwQtgzmLB2__i0vdGYnfgA"
}
```

#### Response fields

<table><thead><tr><th width="205">Path</th><th width="134">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>access_token</code></td><td><code>String</code></td><td>For Login flows, the access token will be a random string that is not to be used.<br><br>For Myinfo flows, the token will be an encoded JSON Web Token (JWT). This token is to be used to exchange for payload at UserInfo Endpoint.</td></tr><tr><td><code>token_type</code></td><td><code>String</code></td><td>The type of token being requested, Bearer only so far.</td></tr><tr><td><code>id_token</code></td><td><code>String</code></td><td>The ID token with relevant claims in JWT format signed by the ASP. Note that the example response body shows a JWS (3-part structure separated by dots), but the format will differ for a JWS in JWE (5-part structure). Refer <a href="#id-token-structure">here</a> for more details about the ID token structure.</td></tr></tbody></table>

### **Error Response**

Singpass generally follows OIDC error response specifications. For more information, please refer to [Token Error Response specifications](https://tools.ietf.org/html/rfc6749#section-5.2).

## ID Token Structure

The format and structure of the issued ID Token will vary depending on the client’s profile as specified in this table below:

| Client Profile                            | `sub` Claim Content                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | ID token format                                                                                                |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `direct`                                  | UUID only (eg. `u=32af8b7d-ad1d-4c25-8dc7-0a981b533000`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | JWS                                                                                                            |
| `direct_pii_allowed`                      | <p><strong>Regular NRIC holders</strong>: NRIC and UUID (eg. <code>s=S1234567A,u=32af8b7d-ad1d-4c25-8dc7-0a981b533000</code>)<br></p><p><strong>Singpass Foreign Account (SFA) holders</strong>: Singpass User ID (UID), Foreigner ID (FID), Country-of-Issuance (COI) and UUID (eg. <code>s=Y7613265T,fid=G730Z-H5P96,coi=DE,u=e2af740e-25b4-4b19-b527-494670952cb0</code>)<br></p><p>This class of users were previously known as "Foreign Unique Account" or "Singpass Foreign Unique Account" users. Only designated relying parties are able to have SFA users authenticate & complete token exchange.</p> | <p>JWS in JWE (encrypted with client’s JWK)</p><p>See section below for more details about the JWE format.</p> |
| `bridge` (special case internal use only) | NRIC and UUID (eg. `s=S1234567A,u=32af8b7d-ad1d-4c25-8dc7-0a981b533000`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | JWS                                                                                                            |

### Overview of a JWS in JWE

An encrypted ID token will returned from the `/token` endpoint is a JWS in JWE that is in compact serialization form. It has the following structure:

* JWE Header
* Encrypted Key
* Initialization Vector
* Encrypted Payload (if decrypted, this would be the Base64 encoded form of a [JWS ID Token](#overview-of-jws))
* Authentication Tag

See <https://datatracker.ietf.org/doc/html/rfc7516#section-3.1> for more details.

### JWE Header

The JWE will contain these standard headers. Refer to <https://tools.ietf.org/html/rfc7515#section-4> for more information about each header.

> Note: Relying parties should use the `kid` field in the header to determine which key NDI used for encryption.

*example*

```json
{
  "kid": "client_01",
  "cty": "JWT",
  "enc": "A256CBC-HS512",
  "alg": "ECDH-ES+A256KW"
}
```

### Overview of JWS

The [JWS](https://tools.ietf.org/html/rfc7515) ID token returned from the `/token` endpoint is in compact serialization form. A JWS has the following structure.

* JWS Header
* Payload (containing claims)
* Signature

### JWS Header

The JWS ID token will contain these standard headers. Refer to <https://tools.ietf.org/html/rfc7515#section-4> for more information about each header.

*example*

```json
{
  "kid": "ndi_stg_01",
  "typ": "JWT",
  "alg": "ES256"
}
```

### JWS Claims<br>

The JWS ID token will contain the following claims.

*example*

```json
{
  "aud": "gnY6Erichpb5t4NFRP9R4L7aEC9N0FQH",
  "sub": "s=S8829314B,u=1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
  "amr": ["pwd", "swk"],
  "iss": "https://stg-id.singpass.gov.sg",
  "exp": 1727322545,
  "iat": 1727321945,
  "nonce": "L5nmQfcetDDIeincoqvCrFyGv+nHobkv4XocNYPCXaQ="
}
```

*Table 1. Description of Claims*

| Path    | Type     | Description                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sub`   | `String` | The principal that is the subject of the JWT. Contains a comma-separated, key=value mapping that identifies the user; possibly including multiple alternate IDs representing the user. The keys included vary by the profile of the OIDC client, and the user type, however the minimal format is `u=<UUID>` where UUID represents the user’s globally unique identifier. <https://tools.ietf.org/html/rfc7519#section-4.1.2> |
| `aud`   | `String` | The client\_id of the relying party. <https://tools.ietf.org/html/rfc7519#section-4.1.3>                                                                                                                                                                                                                                                                                                                                      |
| `iss`   | `String` | The principal that issued the JWT. <https://tools.ietf.org/html/rfc7519#section-4.1.1>                                                                                                                                                                                                                                                                                                                                        |
| `iat`   | `Number` | The time at which the JWT was issued. <https://tools.ietf.org/html/rfc7519#section-4.1.6>                                                                                                                                                                                                                                                                                                                                     |
| `exp`   | `Number` | The expiration time on or after which the JWT MUST NOT be accepted for processing. Defaults to 10 minutes since "iat". <https://tools.ietf.org/html/rfc7519#section-4.1.4>                                                                                                                                                                                                                                                    |
| `amr`   | `Array`  | Authentication method references. Example values are `["face"]`, `["fv"]`, `["fv-alt"]`, `["otp"]`, `["pwd","fv"]`, `["pwd","otp-email"]`, `["pwd","sms"]`, `["pwd","swk"]`, `["pwd"]`, `["sso"]`. Note that this list is non-exhaustive, and NDI reserves the right to introduce new values without prior notice to RPs.                                                                                                     |
| `nonce` | `String` | String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. <https://openid.net/specs/openid-connect-core-1_0.html#IDToken>                                     |
| `acr`   | `String` | (Optional) The Authentication Context Class Reference. The values are context-specific and agreed upon between NDI and relying parties when used.                                                                                                                                                                                                                                                                             |
