Custom Integration

If your app is written in a language other than Javascript, you'll need to write your own OAuth integration code to integrate with sgID.

Creating an authorization URL

To allow your user to login into your app with sgID, you need to create an sgID authorization URL to redirect your user to so that they can authenticate with Singpass. You will need to supply the following query string parameters:

Example URL:

https://api.id.gov.sg/v1/oauth/authorize?
    response_type=code
    &client_id=abc
    &scope=openid%20myinfo.name%20myinfo.passport_expiry_date%20myinfo.nric_number
    &redirect_uri=https://example.com/callback
    &nonce=BQO8SV3ALIYA808IZ8O7PKWRI8A8X6MI
    &state=tk39drykro3

Exchange auth code for access token

After the user authenticates with Singpass, sgID will redirect the user back to the callback URL you provided, together with the authorization code and a state value. Reusing the callback URL defined in the example above:

http://localhost:3000/other_callback?code=someauthcode&state=somestate

Using this authorization code, we can use the SDK to exchange it for an access token, which will be used to retrieve user information. To do so, make a POST request to

https://api.id.gov.sg/v1/oauth/token

with the following request body parameters:

You should receive a response with the following attributes:

Example JSON response body:

{
    "access_token": "I6zGnxYTy4fZubtb7LcG48K1fHWb5b",
    "id_token": "eyJhbGciOiJ...[truncated]...L6zm6LaWfkBoA",
}

The ID token is signed with sgID's private key. It is highly recommended that you verify the ID token with our public keys, which are available at https://api.id.gov.sg/.well-known/jwks.json

Request for user info

Once you have the access token, you can use it to request information about the user corresponding to the scopes that you requested. To do so, make a GET request to

https://api.id.gov.sg/v1/oauth/userinfo

with the access token you received in the previous step. Example request:

GET /v1/oauth/userinfo HTTP/1.1
Host: api.id.gov.sg
Authorization: Bearer I6zGnxYTy4fZubtb7LcG48K1fHWb5b
Content-Length: 57
Content-Type: application/json

You should receive a response with the following attributes:

Example JSON response body:

{
    "sub": "abcdef",
    "key": "eyJhbGcDpgYRL4chyXTjgim...[truncated]...Gxa2tO7nghnu-ewD5ZqA",
    "data": {
        // Note: this will contain all the scopes you requested
        "myinfo.nric_number": "eyJlbmMiOiJ...[truncated]...QafqHmGERc3A",
        "myinfo.name": "eyJlbmMiOi...[truncated]...UgJ9hDSTNLVw",
        "myinfo.passport_expiry_date": "eyJlbmMiOi...[truncated]...UvS41pKk9VKQ",
    }
}

Decrypting the payload

As part of sgID's privacy-preserving measures, user data is transmitted in encrypted form, so that the sgID server is unable to read the data being transacted. The data is encrypted with a block key, which is itself encrypted with your client's public key so that only your client has access to the block key.

Therefore, to obtain the user data in plaintext, you will need to:

  1. Decrypt the key received from the user info response with your client private key. This will give you the block key

  2. Decrypt the data received from the user info response with the block key you have just obtained

Examples decryption:

// We use the node-jose package for working with JWEs and JWKs
// https://github.com/cisco/node-jose
import { JWE, JWK } from 'node-jose'

/**
* Decrypts data into an object of
* plaintext key-value pairs
*
* @param {string} encKey - encrypted block key
* @param {array} block - data
* @param {string} privateKeyPem - private key in pem format
* @returns {object}
*/
async function decryptData(encKey, block, privateKeyPem) {
 const result = {}
 
 // Decrypted encKey to get block key
 const privateKey = await JWK.asKey(privateKeyPem, 'pem')
 const key = await JWE.createDecrypt(privateKey).decrypt(encKey)
 
 // Parse the block key
 const decryptedKey = await JWK.asKey(key.plaintext, 'json')
 
 // Decrypt data
 for (const [key, value] of Object.entries(block)) {
   const { plaintext } = await JWE.createDecrypt(decryptedKey).decrypt(value)
   result[key] = plaintext.toString('ascii')
 }

 return result
}

Example of decrypted data:

{
  "myinfo.name": "TIMOTHY TAN CHENG GUAN",
  "myinfo.nric_number": "S3000786G",
  "myinfo.passport_expiry_date": "2024-01-01",
}

Last updated