# ID Token

The **ID Token** is a verifiable security token issued by Corppass after a successful authorization code exchange. It contains identity-related claims about the authenticated **Entity** and the **Acting User**.

The ID Token is returned as a **JSON Web Encryption (JWE) object**, encrypted using the client's public encryption key configured in their JWKS object / JWKS endpoint.

Inside the encrypted payload lies the **Signed JWT (JWS)**, signed using Corppass' private signing key.

To securely access this information, you must process the token in *three sequential steps*:

1. **Decrypt** the outer JWE layer.
2. **Verify** the inner JWS signature.
3. **Validate before using** the JWT claims.

## 1. Outer Layer: JWE (Encryption)

The outer JWE layer protects the confidentiality of the token. The payload is encrypted using the Client's Public Encryption Key (retrieved by Corppass from the Client's JWKS).

It consists of *five dot-separated Base64URL-encoded parts*.

<pre class="language-plaintext"><code class="lang-plaintext"><strong>&#x3C;protected header>.&#x3C;encrypted key>.&#x3C;initialisation vector>.&#x3C;ciphertext>.&#x3C;authentication tag>
</strong></code></pre>

Refer to [JWS and JWE](/technical-specifications/technical-concepts/jws-and-jwe.md#json-web-encryption-jwe) section for more details.

### JWE Protected Header

<table><thead><tr><th width="154.92578125">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>alg</code></td><td>Key Management Algorithm. The algorithm used to encrypt the key that encrypts the payload.</td></tr><tr><td><code>enc</code></td><td>Content encryption algorithm. The algorithm used to encrypt the payload.</td></tr><tr><td><code>typ</code></td><td>Token type. Always set to <code>JWT</code>.</td></tr><tr><td><code>kid</code></td><td>Key ID. Identifies which Public Encryption Key from the RP's JWKS was used by Corppass to encrypt this token.</td></tr></tbody></table>

{% hint style="warning" %}
Clients **must** use the **`kid`** field in the JWE header to identify which Private Encryption Key from their local keystore is required to decrypt the payload.
{% endhint %}

#### Sample JWE Header

```json
{
  "alg": "ES256",
  "enc": "A256CGM",
  "typ": "JWT",
  "kid": "example-key-id"
}
```

### Action: Decrypt

1. Read the `kid` from the JWE header.
2. Look up the corresponding Private Encryption Key in your local keystore.
3. Decrypt the token using that private key.
4. The decrypted payload is a [Signed JWT (JWS)](#id-2.-inner-layer-jws-signature).

## 2. Inner Layer: JWS (Signature)

After decrypting the JWE in Step 1, the resulting payload is a Signed JWT (JWS). This layer ensures the token was issued by Corppass and has not been tampered with.

Refer to the [JWS and JWE](/technical-specifications/technical-concepts/jws-and-jwe.md#json-web-encryption-jwe) section for more details.

### JWT Header

<table><thead><tr><th width="172.67578125">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>alg</code></td><td>Signing Algorithm. The algorithm used by Corppass to sign the token.</td></tr><tr><td><code>typ</code></td><td>Token type. Always set to <code>JWT</code>.</td></tr><tr><td><code>kid</code></td><td>Key ID. Identifies the key used for signing. </td></tr></tbody></table>

#### Sample JWT Header

```json
{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "example-key-id"
}
```

### Action: Verify Signature

1. Read the `kid` from the JWS header.
2. Retrieve the matching Corppass Public Key from the [JWKS Endpoint](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/0.-well-known-endpoints/jwks-endpoint.md).
3. Verify the signature of the JWS using that public key.
4. If the signature is valid, you can now trust the content. The payload is the [JWT Claims Set](#jwt-claims-payload).

## 3. JWT Claims (Payload)

The decoded JSON payload from Step 2 contains the identity data, organized into three logical sections: **Metadata**, **Entity Identity**, and **Actor (Acting User) Identity**.

### Action: Validate

1. Validate the standard claims (`iss`, `aud`, `exp`, `nonce`) against your expected values.
   1. `iss`: Must match the `issuer` URL in Corppass' [OpenID Discovery Endpoint](/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/0.-well-known-endpoints/openid-discovery-endpoint.md).
   2. `aud`: Must match your assigned Client ID.
   3. `exp`: Must be after current time (not expired).
   4. `nonce`: Must match the `nonce` value sent in the initial Authorization Request.
2. Validate the `at_hash` to ensure the Access Token is bound to this ID Token.
   1. `at_hash`: Must match the hashed value of the Access Token.

{% hint style="warning" %}
Before using this data, you **must validate the standard claims** to ensure the token is meant for you and is still valid.

For clients using a [**certified OIDC Relying Party library**](https://openid.net/developers/certified-openid-connect-implementations/), these checks will be automatically performed by the library, though clients must still pass the nonce to the library for it to perform the nonce check.
{% endhint %}

### A. Token Metadata

<table><thead><tr><th width="156.11871337890625">Claim</th><th width="120.90472412109375">Type</th><th width="471.766357421875">Description</th></tr></thead><tbody><tr><td><code>iss</code></td><td>String</td><td><p>The issuer identifier of the Corppass authorization server.</p><p></p><p><strong>Validation Required: Must Match:</strong> The <code>issuer</code> URL in Corppass' <a href="/pages/IwZgaFw7FMqg56MsCuSh">OpenID Discovery Endpoint</a>.</p></td></tr><tr><td><code>aud</code></td><td>String</td><td><p>Audience. The client ID of your client application as registered during onboarding.</p><p></p><p><strong>Validation Required: Must Match:</strong> Your assigned Client ID.</p></td></tr><tr><td><code>iat</code></td><td>Number</td><td>Issued at. The unix timestamp, in seconds, at which the token was issued.</td></tr><tr><td><code>exp</code></td><td>Number</td><td><p>Expiration time. The unix timestamp, in seconds, on or after which this token must not be accepted for processing.</p><p></p><p><strong>Validation Required: Must Check:</strong> Current time &#x3C; <code>exp</code>.</p></td></tr><tr><td><code>nonce</code></td><td>String</td><td><p>Nonce. The string value used to associate a Client session with an ID Token, and to mitigate replay attacks. Matches the value sent in the authorization request.</p><p></p><p><strong>Validation Required: Must Match:</strong> The <code>nonce</code> value sent in the initial Authorization Request.</p></td></tr><tr><td><code>amr</code></td><td>String Array</td><td>Authentication methods references used during Singpass Login.<br><br>Refer to <a href="#supported-amr-values">Supported AMR Values</a> section for list of supported values.</td></tr><tr><td><code>at_hash</code></td><td>String</td><td><p>Hash value of this access token.</p><p></p><p>Refer to <a href="https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken">Section 3.1.3.6 of OIDC Core 1.0</a> for more details.</p><p></p><p><strong>Validation Required: Must Verify:</strong> Hash the Access Token and compare to this value. This validates the ID Token and Access Token's integrity, ensuring they have not been tampered with.</p></td></tr></tbody></table>

#### Supported `AMR` Values

{% hint style="info" %}
The list is non-exhaustive. Singpass reserves the right to introduce new values without prior notice.
{% endhint %}

<table><thead><tr><th width="250.109375">Value</th><th>Description</th></tr></thead><tbody><tr><td>pwd</td><td>Password</td></tr><tr><td>otp-sms</td><td>SMS OTP</td></tr><tr><td>face</td><td>Face verification</td></tr><tr><td>face-alt</td><td>Alternative form of face verification.</td></tr><tr><td>swk</td><td>Software-secured key (i.e. QR/shortcut login via Singpass app)</td></tr><tr><td>hwk</td><td>Hardware-secured key (i.e. QR/shortcut login via Singpass app)</td></tr></tbody></table>

### B. Entity Identity (`sub`, `sub_attributes`)

These claims identify the **Entity** that the user is transacting on behalf of. In Corppass, the primary `sub` of the token is *always* the Entity.

<table><thead><tr><th width="156.11871337890625">Claim</th><th width="120.90472412109375">Type</th><th width="471.766357421875">Description</th></tr></thead><tbody><tr><td><code>sub</code></td><td>String</td><td><p>Subject identifier - the unique identifier of the entity. Represents the principal entity that is the subject of the JWT.<br></p><ul><li>For UEN entity type, this is the UEN.</li><li>For Non-UEN entity type, this is the Corppass Entity ID.</li></ul></td></tr><tr><td><code>sub_type</code></td><td>String</td><td><p>Subject type. Defines the type of the primary subject.<br></p><p>Always set to <code>entity</code>.</p></td></tr><tr><td><code>sub_attributes</code></td><td>JSON</td><td><p>Primary subject information. Contains profile information about the subject (entity) identified by the <code>sub</code> claim.</p><p></p><p><em>Optional.</em> Returned only if primary subject (entity) attributes are present - depending on the requested scopes.</p><p></p><p>Refer to <a href="#sub_attributes-fields"><code>sub_attributes</code> Fields</a> section below for more details.</p></td></tr></tbody></table>

#### `sub_attributes` Fields

Contains information about the Subject (Entity) identified by the `sub` claim.

{% hint style="info" %}
This claim is only returned if it contains data (i.e., non-empty).

The presence of specific fields depends on the scopes requested (e.g., `entity.identity`).
{% endhint %}

<table><thead><tr><th width="186.17578125">Claim</th><th width="90.10003662109375">Type</th><th width="471.8280029296875">Description</th></tr></thead><tbody><tr><td><code>entity_type</code></td><td>String</td><td><p>The entity type.</p><p></p><p>Available values:</p><ul><li><code>UEN</code></li><li><code>NON-UEN</code></li><li><code>GSTN</code></li></ul><p></p><p><em>Required scope:</em> <code>entity.identity</code></p></td></tr><tr><td><code>entity_reg_number</code></td><td>String</td><td><p>Entity registration number.</p><p></p><p><em>Required scope:</em> <code>entity.identity</code></p></td></tr><tr><td><code>entity_coi</code></td><td>String</td><td><p>Country of incorporation.</p><p>Two-letter country code (e.g., <code>SG</code>, <code>MY</code>).</p><p></p><p><em>Required scope:</em> <code>entity.identity</code></p></td></tr><tr><td><code>entity_name</code></td><td>String</td><td><p>The name of the registered entity.</p><p></p><p><em>Required scope:</em> <code>entity.basic_profile.name</code></p></td></tr><tr><td><code>entity_uen_status</code></td><td>String</td><td><p>The UEN entity status.<br>Returned only if the represented entity is of type <code>UEN</code>.</p><p></p><p>Available values:</p><ul><li><code>Registered</code></li><li><code>Deregistered</code></li><li><code>Withdrawn</code></li></ul><p></p><p><em>Required scope:</em> <code>entity.basic_profile.uen_status</code></p></td></tr></tbody></table>

#### Sample Entity Claims

<details>

<summary>Sample claims for a Singapore-registered Entity</summary>

```json
{
  ...
  "sub": "T09LL0001B",
  "sub_type": "entity",
  "sub_attributes": {
    "entity_type": "UEN",
    "entity_reg_number": "T09LL0001B",
    "entity_coi": "SG",
    "entity_name": "My Example Company",
    "entity_uen_status": "Registered"
  }
}
```

</details>

<details>

<summary>Sample claims for a Foreign Entity</summary>

```json
{
  ...
  "sub": "C19001125A",
  "sub_type": "entity",
  "sub_attributes": {
    "entity_type": "NON-UEN",
    "entity_reg_number": "202219428Z",
    "entity_coi": "MY",
    "entity_name": "My Example Malaysian Company"
  }
}
```

</details>

### C. Actor Identity (`act.sub`, `act.sub_attributes`)

These claims identify the **Acting User** performing the transaction on behalf of the Entity.

<table><thead><tr><th width="156.11871337890625">Claim</th><th width="120.90472412109375">Type</th><th width="471.766357421875">Description</th></tr></thead><tbody><tr><td><code>act</code></td><td>JSON</td><td><p>Represents the actor acting on behalf of the primary subject identified by the <code>sub</code> claim.</p><p>In this case, this refers to the acting user performing the transaction on behalf of the entity.</p><p></p><p>Refer to <a href="#act-fields"><code>act</code> Fields</a> section below for more details.</p></td></tr></tbody></table>

#### `act` Fields

<table><thead><tr><th width="160.41796875">Claim</th><th width="95.80316162109375">Type</th><th width="490.25390625">Description</th></tr></thead><tbody><tr><td><code>sub</code></td><td>String</td><td><p>Subject identifier. Represents the actor, which is the user acting on behalf of the primary entity subject.<br></p><p>This is the unique identifier of the user.</p></td></tr><tr><td><code>sub_type</code></td><td>String</td><td><p>Subject type. Defines the type of the actor subject.<br></p><p>Value: <code>"user"</code>.</p></td></tr><tr><td><code>sub_attributes</code></td><td>JSON</td><td><p>Actor subject information. Contains profile information about the subject (user) identified by the <code>act.sub</code> claim.</p><p></p><p><em>Optional.</em> Returned only if actor subject (user) attributes are present - depending on the requested scopes.<br></p><p>Refer to <a href="#act.sub_attributes-fields"><code>act.sub_attributes</code> Fields</a> section below for more details.</p></td></tr></tbody></table>

#### `act.sub_attributes` Fields

Contains information about the Subject (User) identified by the `act.sub` claim.

{% hint style="info" %}
This claim is only returned if it contains data (i.e., non-empty).

The presence of specific fields depends on the scopes requested (e.g., `user.identity`).
{% endhint %}

<table data-full-width="false"><thead><tr><th width="229.11328125">Claim</th><th width="104.25">Type</th><th width="415.97265625">Description</th></tr></thead><tbody><tr><td><code>account_type</code></td><td>String</td><td><p>The acting user's Singpass account type.</p><p></p><p>Available values:</p><ul><li><code>standard</code> - Singpass account holder (Singapore Citizen, PR, FIN holder)</li><li><code>foreign</code> - Singpass Foreign Account holder</li></ul><p></p><p><em>Required scope:</em> <code>user.identity</code></p></td></tr><tr><td><code>identity_number</code></td><td>String</td><td><p>The acting user's identity number.</p><p></p><ul><li><code>account_type="standard"</code> - NRIC or FIN number.</li><li><code>account_type="foreign"</code> - Foreign ID number (e.g., Passport or National ID)</li></ul><p></p><p><em>Required scope:</em> <code>user.identity</code></p></td></tr><tr><td><code>identity_coi</code></td><td>String</td><td><p>Country of issuance of the acting user's identity.</p><p>Two-letter country code (e.g., <code>SG</code>, <code>MY</code>).</p><p></p><p><em>Required scope:</em> <code>user.identity</code></p></td></tr><tr><td><code>name</code></td><td>String</td><td><p>The acting user's full name.</p><p></p><p><em>Required scope:</em> <code>user.name</code></p></td></tr></tbody></table>

#### Sample Acting User Claims

<details>

<summary>Sample claims for an SC/PR / FIN user</summary>

```json
{
  ...
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_type": "user",
    "sub_attributes": {
      "account_type": "standard", 
      "identity_number": "S1234567P", // NRIC / FIN
      "identity_coi": "SG",
      "name": "John Grisham"
    }
  }
}
```

</details>

<details>

<summary>Sample claims for an SFA user</summary>

<pre class="language-json"><code class="lang-json">{
  ...
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_type": "user",
    "sub_attributes": {
      "account_type": "foreign",
      "identity_number": "K28394589", // Foreign ID
      "identity_coi": "MY",
      "name": "John Grisham"
    },
<strong>  }
</strong>}
</code></pre>

</details>

### Sample Full JWT Payload

<details>

<summary>An SC/PR user, acting on behalf of a Singapore-registered company</summary>

```json
{
  "iss": "https://stg-id.corppass.gov.sg",
  "aud": "vOIljWVrGyBMK6f31QYq",
  "iat": 1623162109,
  "exp": 1623165709,
  "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU=",
  "amr": ["pwd", "sms"],
  "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
  "sub": "T09LL0001B",
  "sub_type": "entity",
  "sub_attributes": {
    "entity_type": "UEN",
    "entity_reg_number": "T09LL0001B",
    "entity_coi": "SG",
    "entity_name": "My Example Company",
    "entity_uen_status": "Registered"
  },
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_type": "user",
    "sub_attributes": {
      "account_type": "standard", 
      "identity_number": "S1234567P",
      "identity_coi": "SG",
      "name": "John Grisham"
    }
  }
}
```

</details>

<details>

<summary>An SC/PR user, acting on behalf of a foreign company</summary>

```json
{
  "iss": "https://stg-id.corppass.gov.sg",
  "aud": "vOIljWVrGyBMK6f31QYq",
  "iat": 1623162109,
  "exp": 1623165709,
  "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU=",
  "amr": ["pwd", "sms"],
  "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
  "sub": "C19001125A",
  "sub_type": "entity",
  "sub_attributes": {
    "entity_type": "NON-UEN",
    "entity_reg_number": "202219428Z",
    "entity_coi": "MY",
    "entity_name": "My Example Malaysia Company"
  },
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_type": "user",
    "sub_attributes": {
      "account_type": "standard", 
      "identity_number": "S1234567P",
      "identity_coi": "SG",
      "name": "John Grisham"
    }
  }
}
```

</details>

<details>

<summary>An SFA user, acting on behalf of a Singapore-registered company</summary>

```json
{
  "iss": "https://stg-id.corppass.gov.sg",
  "aud": "vOIljWVrGyBMK6f31QYq",
  "iat": 1623162109,
  "exp": 1623165709,
  "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU=",
  "amr": ["pwd", "sms"],
  "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
  "sub": "T09LL0001B",
  "sub_type": "entity",
  "sub_attributes": {
    "entity_type": "UEN",
    "entity_reg_number": "T09LL0001B",
    "entity_coi": "SG",
    "entity_name": "My Example Company",
    "entity_uen_status": "Registered"
  },
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_type": "user",
    "sub_attributes": {
      "account_type": "foreign", 
      "identity_number": "K28394589",
      "identity_coi": "MY",
      "name": "John Grisham"
    }
  }
}
```

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.corppass.gov.sg/technical-specifications/corppass-authorization-api-fapi-2.0/integration-guide/3.-token-endpoint/id-token.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
