# 4. Parsing the ID Token

The ID token returned during token exchange is a JSON Web Signature (JWS), which is also encrypted using JSON Web Encryption (JWE). The JWE and JWS components are represented using compact serialization form, as specified in [section 3.1 of RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516#section-3.1).

To reduce complexity, we recommend that you use a JWT parsing library to decrypt the token and to verify the signature, instead of implementing the decryption and signature verification yourself. You may refer to [this list](https://www.jwt.io/libraries?support=verify\&algorithm=es256) to look for a suitable JWT library for your programming language, though you must also ensure that your library of choice supports decryption of JWTs encrypted using JWE.

A sample ID token is shown below:

<details>

<summary>Sample encrypted ID token (this is a JWE)</summary>

{% code overflow="wrap" %}

```
eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJraWQiOiJteS1lbmMta2V5IiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoidGIza3UwRHNyUTdFdHRkaVplcDVHUGdUOFgxaW5jYmR3M2YwSHB3ZHQ2SSIsInkiOiJEZzRFXzAwaWxGdEliVFhwMGRTcU84YUJTQXJPT3JSVWNOT3hKcFdReFFRIn19.hNnmqs2S9-vYzEXr7-Q9TmrzddUylJx-_n_HLjMWLQgeGECrwnmYPP2P638dQVGnlr_2KhJh6rFrm2wdoofSXv3YQR49zTC9.oEBMccRGsNtmMpbV1Oh6SA.TtYVOpO8cH1FpADKDIf0paepf7U8Rz2I_QYK2zITAk2wtuMuL3A3sUquvAA9a_KSm23F2WQ5vE0lA1qB54m9IYglWmaLT6epUQGw2gpkdQhrQmCuYMll2uvj1G-OV2TZgtLCNZakZY09U5SpSAjS32Sfa3H9iWmzjj8LiMQ_jLKBN-84TJpwiopOHW3b0O4tvEVdIq6bEfzqRjZ9qcqxkeWnyfajOC-NlCeqalXHJ_uRrJmVVRXmMjvMMOi1AEQJgVlyFaxBSvpkZGdXhuRRCv6LR3RK_SKizIDy1IwP0O5P6s4BKPXNaqjMwdoRKJdwB5euOGN3lK2Nh8hnzBO5Mp_lHf5XbbvdQ5oLWYDPRy19m6sSYPUIP4PfEguY7KmgbTjRd1ciptIetsRtQhKtYs6oLFp1hziwfk9AUYhkLN4dphSfamAZQeFct4lG4-Q865NezAupIs1BgTYPEu8q4Bw0-NXm0S0wISOJxmOCVng3h2uH4lqQY_bS7fGu4b8E30_-NWPesfKGc-dtVG3x7S1vEfDqjqnxY6XzzDnJOv-Op4cfjnATX067utxIJF2o9lo_9AG3Cul3xyGk9gx3DwHKImX_ldaJAajosUQQ8XUyuIsRjO_L8fQG2N9bYsKHt9qX2fK8fZGEfKsERw35i9AOifdlWxT5rcDM202zvZl9RGZOLGv3Z9JUfwtgAPKrEn5bXHwP__tefKgo9Ba__UUivtDzNwQE-ZVMoyKWMTKFLrULlfC9PaBO0UbVp4JsTVjcNgEVsLED-Y3rCBjNjehAGVyJxRcKUsngAEUAZJvNjGxaq149-Q6I7X6JOhrofY_8hqc74d4ROrDZ4vun2qVCzmrQmcCakEGpp9Y2wat0I-v7Q05COWWWu4EL_wdtgRnazA0DnH3el8ZUohsqNQ.fh6pMCnKcgo9yodwB5dPKcxdTZftoEmHKkB-KRxZPiI
```

{% endcode %}

</details>

<details>

<summary>Sample decrypted ID token (this is a JWS)</summary>

{% code overflow="wrap" %}

```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFsaWFzL3N0Zy1zcC1hdXRoLWFwaS1pZC10b2tlbi1zaWduaW5nLWtleS1rbXMtYXN5bW1ldHJpYy1rZXktYWxpYXMifQ.eyJpYXQiOjE3Njk3Mzk4MjMsImV4cCI6MTc2OTc0MDQyMywiaXNzIjoiaHR0cHM6Ly9zdGctaWQuc2luZ3Bhc3MuZ292LnNnL2ZhcGkiLCJzdWIiOiI3YzljNzJlYy01YmUyLTQ5NWEtYTc4ZS02MWU4MDlhMmEyMzYiLCJhdWQiOiJSc3JPeTJpQjBlZFI1M1RKU3VENVVMYWQxcEdtclZaTCIsImFtciI6WyJwd2QiXSwic3ViX2F0dHJpYnV0ZXMiOnsibmFtZSI6Ik5BTUUgT0YgUzU0MTA4MjhBIn0sInN1Yl90eXBlIjoidXNlciIsIm5vbmNlIjoiV2pxVU1NbU9ldllPWG13QzI3bWNBUVdzRkJuM083d3doYlJJdGwyRkprOCIsImFjciI6InVybjpzaW5ncGFzczphdXRoZW50aWNhdGlvbjpsb2E6MSJ9.pI0nhe0BLToMbuZ-9lpQiBNj2sj_ZWJwYFMT83u4yewZupTClPLUeYp08RR_sV2I70H6qpVmydRmoH8R9SipHQ
```

{% endcode %}

</details>

<details>

<summary>Sample decoded ID token payload</summary>

```json
{
  "sub": "7c9c72ec-5be2-495a-a78e-61e809a2a236",
  "aud": "RsrOy2iB0edR53TJSuD5ULad1pGmrVZL",
  "acr": "urn:singpass:authentication:loa:1",
  "sub_type": "user",
  "sub_attributes": { "name": "NAME OF S5410828A" },
  "amr": ["pwd"],
  "iss": "https:\/\/stg-id.singpass.gov.sg\/fapi",
  "exp": 1769740423,
  "iat": 1769739823,
  "nonce": "WjqUMMmOevYOXmwC27mcAQWsFBn3O7wwhbRItl2FJk8"
}
```

</details>

## JWT Claims

The decrypted JWT will have the following claims:

<table><thead><tr><th width="152.11328125">Claim</th><th width="297">Description</th><th>Data Type</th></tr></thead><tbody><tr><td><code>sub</code></td><td>The principal that is the subject of the JWT. Contains a globally unique identifier for the user.</td><td>String</td></tr><tr><td><code>sub_type</code></td><td>A property to describe what is the type of the subject of the JWT.</td><td>For Singpass, this is always the string <code>"user"</code></td></tr><tr><td><code>sub_attributes</code></td><td>This contains some information related to the user's identifiers. This is only returned if the <code>user.identity</code> , <code>name</code>, <code>email</code>, or <code>mobileno</code> scope is requested.</td><td>Object containing key-value pairs. See the <a href="#the-sub_account-claim">section below</a> for full details.</td></tr><tr><td><code>act</code></td><td>An object describing actor acting on behalf of the <code>sub</code>. This is currently unused, but this may be used in the future for delegation (e.g. a child acting on behalf of their parent, or vice-versa).<br><br>This object will contain a <code>sub</code> claim, which contains the UUID of the actor. If the <code>user.identity</code> , <code>name</code>, <code>email</code>, or <code>mobileno</code> scope is requested, it will also contain the respective data for the actor in the <code>sub_attributes</code> claim.<br><br>If the <code>sub_attributes</code> claim is returned, the format of <code>sub_attributes</code> is described in the <a href="#the-sub_account-claim">section below</a>.</td><td>An object containing <code>sub</code> and <code>sub_attributes</code> (if requested).</td></tr><tr><td><code>aud</code></td><td>The client ID of your registered client, provided by Singpass during app onboarding.</td><td>A 32-character case-sensitive alphanumeric string.</td></tr><tr><td><code>iss</code></td><td>The issuer identifier of our authorization server.</td><td>String</td></tr><tr><td><code>iat</code></td><td>The unix timestamp, in seconds, at which we issued this JWT.</td><td>Number</td></tr><tr><td><code>exp</code></td><td>The unix timestamp, in seconds, on or after which this JWT <strong>must not</strong> be accepted for processing.</td><td>Number</td></tr><tr><td><code>amr</code></td><td>An array of authentication method references. This refers to the form factors used by the user to authenticate themselves.<br><br>The array will contain all the form factors used for this authentication. For example, if the user authenticated themselves using their password and SMS OTP, then this array will contain both <code>pwd</code> and <code>otp-sms</code>.</td><td><p>Array of strings. The possible values are:</p><ul><li><code>face</code></li><li><code>pwd</code></li><li><code>otp-sms</code></li><li><code>face-alt</code></li><li><code>swk</code> (software key)</li><li><code>hwk</code> (hardware key)</li></ul><p>Note that this list is non-exhaustive, and we reserve the right to introduce new values without prior notice to you.</p></td></tr><tr><td><code>acr</code></td><td>The authentication class reference value used for this transaction. This identifies the level of assurance that this authentication satisfied.</td><td><p>One of the following values:</p><ul><li><p>urn:singpass:authentication:loa:1</p><ul><li>This indicates that only username and password was used for authentication. This will only happen in the staging environment.</li></ul></li><li><p>urn:singpass:authentication:loa:2</p><ul><li>This indicates that 2-factor authentication was used for authentication.</li></ul></li><li><p>urn:singpass:authentication:loa:3</p><ul><li>This indicates that 3-factor authentication, with face verification as a third factor, was used for authentication.</li></ul></li></ul></td></tr><tr><td><code>nonce</code></td><td>This is the same nonce that you used in your authorization request.</td><td>String</td></tr></tbody></table>

<details>

<summary>Sample ID token</summary>

```json
{
  "aud": "gnY6Erichpb5t4NFRP9R4L7aEC9N0FQH",
  "iss": "https://id.singpass.gov.sg/fapi",
  "exp": 1727322545,
  "iat": 1727321945,
  "nonce": "L5nmQfcetDDIeincoqvCrFyGv+nHobkv4XocNYPCXaQ=",
  "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
  "sub_type": "user",
  "sub_attributes": {
    "account_type": "standard",
    "identity_number": "S1234567G",
    "identity_coi": "SG"
  }
}
```

</details>

## The sub\_attributes Claim

The `sub_attributes` claim is an object that will contain the following properties:

<table><thead><tr><th width="151.4296875">Property</th><th>Description</th><th>Data Type</th></tr></thead><tbody><tr><td><code>account_type</code></td><td><p>The Singpass account type for this user.</p><p>For Singpass Foreign Account (SFA) holders, this will be <code>"foreign"</code>.</p><p>For all other Singpass accounts (i.e. those belonging to Singapore Citizens, Permanent Residents, or FIN holders), this will be <code>"standard"</code>.</p></td><td>A string, which is either <code>"foreign"</code> or <code>"standard"</code>. This is only present if the <code>user.identity</code> scope was requested.</td></tr><tr><td><code>identity_number</code></td><td>A string which contains a referencable identifier for the user.<br><br>For Singapore Citizens and Permanent residents, this will be their NRIC.<br><br>For FIN holders, this will be their FIN.<br><br>For Singpass Foreign Account holders, this will be their foreign ID.</td><td>String. This is only present if the <code>user.identity</code> scope was requested.</td></tr><tr><td><code>identity_coi</code></td><td>The country code for the of issuance of the identity.<br><br>For Singapore Citizens, Permanent Residents, and FIN holders, this will be <code>"SG"</code>.<br><br>For Singpass Foreign Account holders, this will be the country code of the country which issued the user's foreign ID.</td><td>2-letter country code. This is only present if the <code>user.identity</code> scope was requested.</td></tr><tr><td><code>name</code></td><td>The user's principal name</td><td>Non-empty string. This will only be present if the <code>name</code> scope is requested.</td></tr><tr><td><code>email</code></td><td>The user's email address</td><td>String. This will only be present if the <code>email</code> scope is requested, and if the user has an email registered with Singpass.</td></tr><tr><td><code>mobileno</code></td><td>The user's mobile number. This will always be a Singapore mobile number, and it will not include the country code.</td><td>Numeric string. This will only be present if the <code>moibleno</code> scope is requested, and if the user has a mobile number registered with Singpass.<br><br>For Singpass Foreign Account (SFA) users, this will always be absent.</td></tr></tbody></table>

<details>

<summary>Sample sub_attributes for Singapore Citizen, Permanent Resident, or FIN holder</summary>

```json
"sub_attributes": {
  "account_type": "standard",
  "identity_number": "S1234567G",
  "identity_coi": "SG",
  "name": "John Doe",
  "email": "johndoe@gmail.com",
  "mobileno": "91231234",
}
```

</details>

<details>

<summary>Sample sub_attributes for Singpass Foreign Account holder</summary>

```json
"sub_attributes": {
  "account_type": "foreign",
  "identity_number": "K28394589",
  "identity_coi": "TK",
  "name": "Larry Doe",
  "email": "larrydoe@gmail.com",
}
```

</details>

## Verification Checks

{% hint style="info" %}
If you are 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 you must pass the nonce to the library for it to perform the nonce check.
{% endhint %}

In order to ensure that the ID token is valid, you must perform the following checks:

1. Verify that the `iss` claim matches the issuer identifier of our authorization server, which you can obtain from the `issuer` field in the [OpenID configuration](https://docs.developer.singpass.gov.sg/docs/technical-specifications/technical-concepts/openid-connect-discovery) of our authorization server.
2. Verify that the `aud` claim matches your client ID.
3. Ensure that the current time is before the timestamp in the `exp` claim.
4. Ensure that the `nonce` claim is the same as the nonce that you had sent in the authorization request.

If you are integrating with Singpass Login, then the flow ends here. If you are integrating with Myinfo (v5), then the next and final step would be to retrieve the user's information using the `/userinfo` endpoint.
