> For the complete documentation index, see [llms.txt](https://docs.corppass.gov.sg/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/3.-token-endpoint.md).

# 3. Token Endpoint

The **Token Endpoint** is used by the client to exchange an authorization code for tokens after the user successfully authenticates. This is a *secure back-channel request* made directly between the client and Corppass.

#### Returned Tokens

* **ID Token:** A signed and encrypted JWE that includes the entity and user information. Relying Parties (RPs) must decrypt it using their private encryption key, then verify the JWT's signature using Corppass' public keys, available at the Corppass JWKS endpoint.
* **Access Token:** A signed JWS intended for the Corppass resource server(s).

#### Supported Grant Types

Grant types define the method by which a client obtains tokens from the authorization server.

For the Open ID Connect (OIDC) authentication flow, Corppass supports the following grant types:

<table><thead><tr><th width="266">Grant Type</th><th>Description</th></tr></thead><tbody><tr><td><code>authorization_code</code> </td><td><p>Used to securely exchange an authorization code for an ID token and Access Token. Requires prior user authentication and is suitable for confidential clients.</p><p></p><p>Refer to <a href="https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 of the OIDC Core Specification </a>for more details.</p></td></tr></tbody></table>

#### Supported TLS Cipher Suites

Please ensure you are using a supported cipher suite for this endpoint. Refer to [Supported TLS Cipher Suites](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/supported-tls-cipher-suites.md) for the full list.

## Request

To obtain the Access Token and ID Token, send a `POST` request to the Token Endpoint URL obtained from the [OpenID Discovery Endpoint](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/0.-well-known-endpoints/openid-discovery-endpoint.md) (`token_endpoint`).

```http
POST /mga/sps/oauth/oauth20/token
```

### Request Headers

<table><thead><tr><th width="225.18359375">Field</th><th width="108.93359375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>Content-Type</code></td><td>Yes</td><td><p>Indicates the encoding format of the request body.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be set to <code>application/x-www-form-urlencoded</code>.</li></ul></td></tr><tr><td><code>DPoP</code></td><td>Yes</td><td><p>DPoP Proof JWT. A distinct JSON Web Token (JWT) that proves the client possesses the private key corresponding to the public key used for token binding.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be signed using the exact same private key as the DPoP proof presented in the Pushed Authorization Request (PAR) step.</li></ul><p></p><p>Refer to <a href="/pages/QOVgmxTpNTkdnLGQCrH6">Demonstrating Proof of Possession</a> section for more details.</p></td></tr></tbody></table>

### Request Body

<table><thead><tr><th width="226.19140625">Field</th><th width="128.8359375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>code</code></td><td>Yes</td><td><p><strong>Authorization Code</strong>. The authorization code issued during the callback step at <a href="/pages/be3ILFlYmJIRS9Hv9uv3">Authorization Endpoint</a>.<br></p><p><strong>Validation:</strong></p><ul><li>The code is valid for 60 seconds. Expired codes will result in an error.</li><li>The code can only be exchanged once.</li></ul></td></tr><tr><td><code>redirect_uri</code></td><td>Yes</td><td><p>The absolute URI used in the initial authorization request (PAR).<br></p><p><strong>Validation:</strong></p><ul><li>Must strictly match the <code>redirect_uri</code> provided in the original Pushed Authorization Request (PAR).</li></ul></td></tr><tr><td><code>grant_type</code></td><td>Yes</td><td><p>The type of authorization grant being requested.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be set to <code>authorization_code</code>.</li></ul></td></tr><tr><td><code>client_id</code></td><td>No</td><td><p>Optional. The unique identifier assigned to the Relying Party during onboarding.<br></p><p><strong>Validation:</strong></p><ul><li>If provided, this value must match the <code>sub</code> (subject) claim in the Client Assertion JWT.</li></ul></td></tr><tr><td><code>client_assertion_type</code> </td><td>Yes</td><td><p>Specifies the format of the client assertion.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be set to <code>urn:ietf:params:oauth:client-assertion-type:jwt-bearer</code>, as mandated by OIDC specifications.</li></ul></td></tr><tr><td><code>client_assertion</code></td><td>Yes</td><td><p>A signed JWT used to authenticate the client application.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be generated afresh for each authorization session.</li><li>Must contain a unique <code>jti</code> (JWT ID) claim to prevent replay attacks.</li><li>Must be signed using the Private Key corresponding to the public key registered in your JWKS.</li></ul><p></p><p>Refer to <a href="/pages/LywRxjs8azw2UCtFr8yf">Client Assertion JWT</a> section for more details.</p></td></tr><tr><td><code>code_verifier</code></td><td>Yes</td><td><p>PKCE Verifier. The cryptographically random string generated by the client to prove possession of the <code>code_challenge</code>.</p><p></p><p><strong>Validation:</strong></p><ul><li>Must be the original plain-text value used to generate the <code>code_challenge</code> provided at PAR.</li></ul><p></p><p>Refer to <a href="/pages/TOFgMaQYGoeEs2HmOyj3">Proof Key of Code Exchange</a> section for more details.</p></td></tr></tbody></table>

### Sample Request

```http
POST /mga/sps/oauth/oauth20/token
Content-Type: application/x-www-form-urlencoded
DPoP: <signed-DPoP-JWT>

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&client_id=51YUlwazLASM7aqMiBNW
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=eyJ...
&code_verifier=hJ1GZ4ekBQ-NNlc4t62KsLqWhM6p1ZpwRc1cwFtoLp
```

## Success Response

If the request is valid, Corppass returns a JSON response containing the tokens.

### Response Body

<table><thead><tr><th width="153">Field</th><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>token_type</code> </td><td>String</td><td><p>Indicates the type of access token issued.</p><p></p><p>Value is always set to <code>DPoP</code>.</p></td></tr><tr><td><code>access_token</code></td><td>JWS</td><td><p><strong>Access Token</strong>. A signed JSON Web Signature (JWS) used to access protected resources (e.g., Userinfo Endpoint).</p><p><br>The token is valid for <strong>10 minutes</strong> from time of issuance.</p><p></p><p>Refer to <a href="/pages/NX2pxTvQX6KT1CkyD41a">Access Token</a> section for more details.</p></td></tr><tr><td><code>id_token</code></td><td>JWE</td><td><p><strong>ID Token</strong>. A signed and encrypted JSON Web Encryption (JWE) containing user and entity identities, and authentication claims.</p><p><br><strong>Processing:</strong></p><ul><li>The RP must decrypt this token using their Private Encryption Key.</li><li>After decryption, the inner JWT signature must be verified using Corppass' public keys.</li></ul><p></p><p>Refer to <a href="/pages/uq6KYicO9NOENcs1EQ9p">ID Token</a> section for more details.</p></td></tr><tr><td><code>scope</code></td><td>String</td><td><p><strong>Granted Scopes</strong>. A space-delimited list of scopes actually granted to the Access Token.</p><p></p><p>This list typically matches the scopes requested during authorization, though the server may grant a subset of requested scopes based on user consent or policy.</p></td></tr><tr><td><code>expires_in</code></td><td>Numeric</td><td><p>The remaining lifetime of the Access Token in seconds.</p><p></p><p>Value is set to <strong>600 seconds</strong>.</p></td></tr></tbody></table>

### Sample Response

```json
{
    "token_type": "DPoP",
    "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InhHbkJ4ZVYtWTFubUJlZi1KXzBTSWNxaU1hR2NlYWJzaVkxZjRJUkMya0kifQ.eyJmYXBpX2Zsb3ciOnRydWUsImFjdCI6eyJzdWIiOiJhOTg2NTgzNy03YmQ3LTQ2YWMtYmVmNC00MmE3NmE5NDY0MjQifSwiY25mIjp7ImprdCI6ImJvWVZEeFhTN1l6bF9yNFAyVnNvY2E5Ujkyd2dUSEdya25zZy13b2F2dnMifSwianRpIjoiV19YTmZQZGNaelIzMUl2aTFLVEJ2Iiwic3ViIjoiMTIwNjc0ODRaIiwiaWF0IjoxNzc2NDA3NDM3LCJleHAiOjE3NzY0MDgwMzYsInNjb3BlIjoib3BlbmlkIGF1dGhpbmZvIHRwYXV0aGluZm8gdXNlci5pZGVudGl0eSB1c2VyLm5hbWUgZW50aXR5LmlkZW50aXR5IGVudGl0eS5iYXNpY19wcm9maWxlLm5hbWUgZW50aXR5LmJhc2ljX3Byb2ZpbGUudWVuX3N0YXR1cyIsImNsaWVudF9pZCI6Ill3bUFmWXNXSURpTWpIbmg5MUpFIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC91c2VyaW5mbyIsImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9hdXRob3JpemF0aW9uLWluZm8iXX0.v8qy-z4D5joXl12HHNs68sLS848KE6tfXm1q2-b3uqRQ0JCbHkHOvGlsGEJ0r1glWtoFcAdXNtuSjMyEbkQ8WA",
    "id_token": "eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJjdHkiOiJKV1QiLCJraWQiOiJkZXYtZW5jIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiYXVkIjoiWXdtQWZZc1dJRGlNakhuaDkxSkUiLCJlcGsiOnsieCI6Ik9VV3NDODJlQnNxYXN5cDFvbDJITmFhRHpOczlQaGpseUFJS0NnQUNCM1UiLCJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieSI6IkJRVDNFZndDQ1llT2JpMlhQN2tTRVk4TEU4QVB6dU85ZDhoOUJWb0Z2MkUifX0.fFbY2sGO4OfpRLZymADtH_TXiZulpK_GR_f2cfTz3-3NTzO1ig7NC1WXbqv4Adk_7-XEz5EQ51naqA2flu3p18c1JpADYekq.egEfNqL7ySZHD0fWfZJYVQ.qyoBjRfPjKGSCHpKLGUxGRuKhDYC1rW53xCnmxDQ8_gocc5BX0gfPjTB_8KAwDIxw8NrgMN91aLF-Rp-wR3qyvCa8aLkX9zCrDfAzMoD6BlNBqHh8dPah5RMe9P0xSFqbPnB2yU2CWNvG-yoCVLWJTsdpoBQ8w37AdCol1bq53emih2ghREt3x8MU1z_mk1UUVZkJYvXGuLr_GvJWoQa7A3ffkaOdXleiOMSu69M65VrYNnfl_RI-SE1x6cSmgSWeeao6PPw0NpitbMSdIkv3AGjLiMoWVWPAJd955k9-gvp_VjANzN38tumOx6jetLkIru-9HenIUDKfw1hZIaJDj-ZdFJHtrZoROig_4LQ62uvKkf_T8yhHsLCAffyO4kOBeGXYtAItdAtcP70vlMjISptOPrhofNSYWHUTo8v_rfOz7F4tG7H2fp3lNKN6vZPLGBeUpSvfgUFsVwbDfZH02NV852Dm9GoAphytgWLRR-Chic2xRUX2jiimqu28Zzf8o4dtWe93_qSKHBt1FgeIOr0zZMKkuzjRPzq5hEt4FgvprNTBy-LOMsULeTH6V25Ni7lfiyp2sqTk6aLdozfh2OQKau4-4hDpBPq-fUGSHAs95vpEr49MhGHNLk_D5RzVFI9IcOMVA8-iSVhERjhmVq6uvAe0l78c0hydRzj4GukTkBQYl3o3342WK98m0Wbl4-ftRsOpgY7H_QdGEbf1Wns18lSovqZyIDQGKikLgxxHGZGlqx1AxrLEswL_C25JRZvzsa3wNQJN465DDYoGHB3LWXHOy_Ndv5zRxYe53KR2KysMiOEBHY5PAu2iQ7FvGiY1-DQVx8_NkA0FGb0D7rT-yD2b75hC2udS3qLQzqklSg2Snf8-n4n9FUaUiyB9oUXMBiWIVQFwED_kIM2_121pfTN3cfgneqv1RpfZm1IdC2RkRiMEyi1Yb5IKj_Mb9IuMFXZhzqIlyhkIePZjQG3gW985ORSppgqjvSGYK93_yb9SZGpx65-RvFDVf-vyV-lfVo2E9UUzFFz6qba7ccviz1_iLLKDDfZe3-SV-VQt_QB4kglFKMyEN8CAZpAPzBsZSZpXgcCA1rvnoDRhjhf2UUMNtTlz7uiBZ5HUS9IzH_830lGGEdqH3KRj6_afY2iSSGDUYd0CrgMwIMghPxV146Yc_AsnJ2Wf7HuQb6GxlbUttLLvImUmnJhTAe8KscGTm6_k6AzN7GYw1fZirlHeG4jCC3_rLPm0rxmSSbIhvcZw-0MSrgq-j_oTapOAk2AeAU4cSKW5V_BeaW-dfXPTiBrv9pH6izY_6vnqX0.0OCnPEeCD5xOt_kPGTRqQypCKGjGdLmnNCOE50BNjGQ",
    "scope": "openid authinfo tpauthinfo user.identity user.name entity.identity entity.basic_profile.name entity.basic_profile.uen_status",
    "expires_in": 599
}
```

## Error Response

If the token exchange fails, Corppass returns a JSON response containing an error code and description.

### Response Body

<table><thead><tr><th width="179.3671875">Field</th><th width="107">Type</th><th width="459.3515625">Description</th></tr></thead><tbody><tr><td><code>error</code></td><td>String</td><td><p>A standardised error code identifying the type of error that occurred.</p><p><br>See <a href="#error-codes">Error Codes</a> below for a complete list of possible values.</p></td></tr><tr><td><code>error_description</code></td><td>String</td><td>A human-readable text description providing additional details about the error.</td></tr></tbody></table>

#### Error Codes

<table><thead><tr><th width="243.5859375">Error Code</th><th width="135.26953125">HTTP Status</th><th>Description</th></tr></thead><tbody><tr><td>invalid_request</td><td>400 / 401</td><td><p>This may be due to one of the following:</p><ul><li>A required request parameter is missing.</li><li>A request parameter is malformed or does not meet the expected format.</li><li>The client’s JWKS endpoint is not reachable.</li><li>The JWKS object is empty or the encryption key is not available.</li><li>The TLS cipher suite used for the connection is not compliant with <a href="https://openid.bitbucket.io/fapi/fapi-2_0-baseline.html#section-4.2">FAPI 2.0 security requirements</a>.</li></ul><p>Please ensure that:</p><ul><li>All required parameters are provided and correctly formatted.</li><li>Your JWKS endpoint is accessible and returns a valid key set that complies with the required <a href="/pages/4HeJT56VKL5MvRg3PAsJ">specification</a>.</li></ul></td></tr><tr><td>invalid_client</td><td>400 / 401</td><td><p>This may be due to one of the following:</p><ul><li>Missing client credential</li><li>Invalid client credential</li><li>Expired client credential</li><li>Improperly formatted client credential or assertion (e.g., malformed JWT)</li><li>The client’s JWKS does not contain the required signing key</li></ul><p>Please ensure that:</p><ul><li>The client credential is present, valid, and not expired</li><li>Your JWKS endpoint is accessible and returns a valid key set that complies with the required <a href="/pages/4HeJT56VKL5MvRg3PAsJ">specification</a>.</li></ul></td></tr><tr><td>invalid_grant</td><td>400 / 401</td><td>The provided authorization code is invalid, expired, revoked, or does not match the authenticated client.</td></tr><tr><td>invalid_dpop_proof</td><td>401</td><td><p>This may due to the one of the following:</p><ul><li>The DPoP proof is expired.</li><li>The DPoP proof is malformed.</li><li>The DPoP proof failed signature verification.</li><li>The DPoP proof contains missing or invalid required claims.</li></ul><p>Please ensure that:</p><ul><li>The DPoP proof is correctly structured and signed using the client's private key.</li><li>The DPoP proof complies with the required <a href="/pages/QOVgmxTpNTkdnLGQCrH6">specification</a>.</li></ul></td></tr><tr><td>unsupported_grant_type</td><td>400</td><td>The grant type is not supported.</td></tr><tr><td>server_error</td><td>500</td><td>The authorization server encountered an unexpected internal error while processing the request.</td></tr><tr><td>temporarily_unavailable</td><td>503</td><td>The server is temporarily unable to handle the request due to maintenance or high load.</td></tr></tbody></table>

### Sample Response

```json
{
  "error": "invalid_dpop_proof",
  "error_description": "DPoP proof iat is not recent enough"
}
```

## Next Steps

#### 1. Retrieve Entity & User Identity (ID Token)

The ID Token contains information about both the *Entity* and the *Acting User* performing the transaction.

**Proceed to** [**ID Token**](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/3.-token-endpoint/id-token.md) **section for decryption steps and claim mapping.**

#### 2. Fetch Additional Data (Userinfo Endpoint)

If your application requires more detailed entity and user attributes not present in the ID Token, use the Access Token to query the Userinfo Endpoint for additional information.

**Proceed to** [**4. Userinfo Endpoint**](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/4.-userinfo-endpoint.md) **to fetch additional data.**
