ID Token Structure

An ID Token is a signed and encrypted JWT (JWE) issued by Corppass in the OpenID Connect Authorization Code Flow. It contains identity-related claims about the authenticated user and their associated entity, and is returned in the token response after successful authentication.

Parsing the ID token

Overview

The ID token is a JSON Web Encryption object (JWE), and the inner encrypted payload is a signed JWT (JWS).

The JWE is encrypted using the RP's public encryption key as configured in their JWKS object / JWKS endpoint, while the JWS is signed using Corppass private signing key.

High Level Steps

To process the ID token, clients must:

  1. Use their private encryption key to decrypt the JWE.

  2. Extract and validate the inner signed JWT (JWS).

    1. Validate the signature using Corppass’s public signing key from the JWKS endpoint.

    2. Validate the following claims: iss, aud, exp, iat, and nonce.

Structure

JWE

A JWE consists of five dot-separated Base64URL-encoded parts:

<protected header>.<encrypted key>.<initialisation vector>.<ciphertext>.<authentication tag>

Read the Key Concepts - JWS and JWE page to find out more about JWEs and how to handle them.

JWE Header

Example of a protected header:

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

Fields present in the JWE header:

Field
Description

alg

Key management algorithm — asymmetric ECDH-ES with AES key wrapping

enc

Content encryption algorithm — AES GCM

typ

Token type (always JWT)

kid

Key ID — matches the encryption public key in the client's JWKS

Signed JWT

After decrypting the ID token (JWE), the payload retrieved will be a signed JWT. RPs must validate the signature.

Read the Key Concepts > JWS and JWE page to find out more about JWSs and how to handle them.

JWT Header

Example of the JWT header:

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

Fields present in the JWT header:

Field
Description

alg

Signing algorithm used by Corppass (e.g., ES256)

typ

JWT token type

kid

Key ID — matches a public key in Corppass’s JWKS for signature validation

JWT Claims

The decrypted JWT will have the following claims:

Claim
Type
Description
Mandatory

sub

String

Represents the subject principal of this JWT, which in this case is the entity that is being accessed on behalf of. It will be the entity's UEN (if it is of UEN type) or the Corppass Entity ID (if not UEN type).

Y

act

Object

Refers to the actor - the underlying user that is acting on behalf of the subject indicated by the sub claim above.

See act for more details.

Y

sub_account

Object

Contains more information related to the subject principal of this token. Should always have account type "entity"

See sub_account - Entity Account for more details.

Y

aud

String

The client ID of your registered client as provided during client onboarding.

Y

iss

String

The issuer identifier of the Corppass authorization server.

Y

iat

Number

The unix timestamp, in seconds, at which the JWT is issued.

Y

exp

Number

The unix timestamp, in seconds, on or after which this JWT must not be accepted for processing.

Y

nonce

String

Refers to the same nonce that clients provided in the initial authorization request.

Y

amr

String Array

Authentication methods references used during Singpass Login. See amr for list of possible values.

Y

at_hash

String

Hash value of this access token. Refer to Section 3.1.3.6 of OIDC Core 1.0.

Y

sub_account

The sub_account claim may appear in two places -

  • As a top-level claim in the ID token, where it represents the identity of the Entity that is being represented. This claim will always be provided.

  • As a nested claim within the act claim, where it represents the identity of the Person that is acting on behalf of the entity. This claim will only be provided if sub_account scope is presented.

Account Types

The sub_account claim is an object that will always contain an account_type property, which reflects the type of account the subject principal holds.

{
    "sub": "...",
    "sub_account": {
        "account_type": "entity", // attribute is always present
        ...,
        ...
    },
    "act": {
        "sub_account": {
            "account_type": "SC/PR"
            ...,
            ...
        }
    } 
}

Entity Account

When this claim exists as a top-level claim in the ID token (ie. not nested inside act claim), the account_type value will always be entity .

Depending on the type of entity, the other values returned in the object will differ as described below.

Sample sub_account claim for a Singapore-registered Entity
{
  "sub": "T09LL0001B",
  "sub_account": {
    "account_type": "entity",
    "entity_name": "My Example Company",
    "entity_type": "UEN",
    "entity_status": "Registered"
  }
}
Sample sub_account claim for a Foreign Entity
{
  "sub": "C19001125A",
  "sub_account": {
    "account_type": "entity",
    "entity_name": "My Example Malaysia Company",
    "entity_type": "NON-UEN",
    "non_uen_country": "Malaysia",
    "non_uen_reg_no": "202219428Z"
  }
}

List of claims for sub_account describing an entity account

Claim
Type
Description
Mandatory

account_type

String

Will always be entity in this case.

Y

entity_name

String

The name of the registered entity.

Y

entity_type

String

The entity type.

List of possible values are UEN, NON-UEN, GSTN

Y

entity_status

String

The entity status.

List of possible values are Registered, Deregistered, Withdrawn . Only provided if the represented entity is of type UEN.

N

non_uen_country

String

The country of which the entity was registered in.

Only provided if the represented entity is a foreign company (NON-UEN entity type).

N

non_uen_reg_no

String

The entity registration number in the foreign jurisdiction.

Only provided if the represented entity is a foreign company (NON-UEN entity type).

N

User Account

When this claim exists as a nested claim in the ID token (ie. nested under act claim), it represents the underlying user that is acting on behalf of the entity.

In this case, the account_type value can be one of the following types, depending on the user's residency status or type of pass that they are holding.

User Account Types

Residency / Pass Type
account_type

Singaporeans/PRs

SC/PR

FIN holder

FIN

Singpass Foreign Account

SFA

Depending on the user's account type, the other values returned in the object will differ as described below.

Sample nested sub_account claim for an SC/PR user
{
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_account": {
      "account_type": "SC/PR", 
      "uinfin": "S1234567P", // represents NRIC
      "name": "John Grisham",
      "email": "[email protected]",
      "email_verified": true,
    }
  }
}
Sample nested sub_account claim for a FIN user
{
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_account": {
      "account_type": "FIN",
      "uinfin": "G0874953C", // represents FIN
      "name": "John Grisham",
      "email": "[email protected]",
      "email_verified": true,
    },
  },
}
Sample nested sub_account claim for an SFA user
{
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_account": {
      "account_type": "SFA",
      "foreign_id": "K28394589",
      "foreign_id_coi": "MY",
      "name": "John Grisham",
      "email": "[email protected]",
      "email_verified": true,
    },
  },
}

List of claims for sub_account describing a user account

Claim
Type
Description
Mandatory

account_type

String

Will always be one of the values in User Account Types.

Y

uinfin

String

The acting user's NRIC or FIN number. Only provided if the user account type is SC/PR or FIN .

N

name

String

The acting user's full name.

Y

email

String

The acting user's email address as registered on Corppass. Only provided if the business_profile.email scope is present in the access token.

N

email_verified

Boolean

Indicates whether the acting user's email has been verified by the user. Only provided if the business_profile.email scope is present in the access token.

N

foreign_id

String

The acting user's foreign ID number (e.g., passport or local ID). Only provided if the user account type is SFA

N

foreign_id_coi

String

The country of issuance of the user's foreign ID. It is a 2-letter country code. Only provided if the user account type is SFA

N

act

The act claim is an object which in itself contains claims that identify the actor.

In Corppass case, the act always refer to the acting user.

Sample act claim
{
  "sub": "T09LL0001B",
  "sub_account": {
    "account_type": "entity",
    "entity_name": "My Example Company"
  },
  "act": {
    "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
    "sub_account": {
      "account_type": "SC/PR",
      "uinfin": "S1234567G"
  }
}
Claim
Type
Description
Mandatory

sub

String

The principal that represents the actor. It is a globally unique identifier of the user.

Y

sub_account

Object

Contains more information related to actor. Will never have account type value "entity"

Only provided if the sub_account scope is requested by the RP in the initial authentication request. See sub_account - User Account for more details.

N

amr

Note that the list below is non-exhaustive, and Singpass reserves the right to introduce new values without prior notice.

Some possible values are:

Value
Description

pwd

Password

otp-sms

SMS OTP

face

Face verification

face-alt

Alternative form of face verification.

swk

Software-secured key (i.e. QR/shortcut login via Singpass app)

hwk

Hardware-secured key (i.e. QR/shortcut login via Singpass app)

ID Token Samples

An SC/PR user, acting on behalf of a Singapore-registered company
{
    "iat": 1623162109,
    "iss": "https://stg-id.corppass.gov.sg",
    "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
    "sub": "T09LL0001B",
    "sub_account": {
      "account_type": "entity",
      "entity_name": "My Example Company",
      "entity_type": "UEN",
      "entity_status": "Registered"
    },
    "act": {
      "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
      "sub_account": {
        "account_type": "SC/PR", 
        "uinfin": "S1234567P"
        "name": "John Grisham",
        "email": "[email protected]",
        "email_verified": true,
      }
    },
    "exp": 1623165709,
    "aud": "vOIljWVrGyBMK6f31QYq",
    "amr": ["pwd", "sms"],
    "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU="
}
An SC/PR user, acting on behalf of a foreign company
{
    "iat": 1623162109,
    "iss": "https://stg-id.corppass.gov.sg",
    "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
    "sub": "C19001125A",
    "sub_account": {
      "account_type": "entity",
      "entity_name": "My Example Malaysia Company",
      "entity_type": "NON-UEN",
      "non_uen_country": "Malaysia",
      "non_uen_reg_no": "202219428Z"
    },
    "act": {
      "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
      "sub_account": {
        "account_type": "SC/PR",
        "uinfin": "S1234567P"
        "name": "John Grisham",
        "email": "[email protected]",
        "email_verified": true,
      },
    },
    "exp": 1623165709,
    "aud": "vOIljWVrGyBMK6f31QYq",
    "amr": ["pwd", "sms"],
    "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU="
}
An SFA user, acting on behalf of a Singapore-registered company
{
    "iat": 1623162109,
    "iss": "https://stg-id.corppass.gov.sg",
    "at_hash": "6J4VlBBQpbAyy1NL4NBW-Q",
    "sub": "T09LL0001B",
    "sub_account": {
      "account_type": "entity",
      "entity_name": "My Example Company",
      "entity_type": "UEN",
      "entity_status": "Registered"
    },
    "act": {
      "sub": "1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
      "sub_account": {
        "account_type": "SFA",
        "foreign_id": "K28394589",
        "foreign_id_coi": "MY",
        "name": "John Grisham",
        "email": "[email protected]",
        "email_verified": true,
      },
    },
    "exp": 1623165709,
    "aud": "vOIljWVrGyBMK6f31QYq",
    "amr": ["pwd", "sms"],
    "nonce": "ZEF+97zc3YZP7huv6nzKspfabDv0wRtce/aVNud23vU="
}

Verification Steps

Standard Claims Verification

In order to ensure that the ID token is valid, clients 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 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.

at_hash Claim Verification

We also strongly encourage clients to use at_hash claim to validate the Access Token's integrity, ensuring it has not been tampered with.

For more details on at_hash claim, refer to Section 2 of the OIDC Core Specification.

Last updated