Singpass Developer Docs
Developer Docs for Singpass
Developer Docs for Singpass
  • INTRODUCTION
    • Overview of Singpass
    • Understanding the basics of OIDC
  • Products
    • Login
      • Introduction
      • Key Principles
      • User Journey
      • Logo Download and Brand Guidelines
      • Singpass Button Guidelines (For developers and designers)
    • FAQ
      • Login
      • Myinfo
    • Myinfo
      • Introduction
      • Key Principles
      • User Journey
      • Logo Download and Brand Guidelines
      • Data Display Guidelines
      • Scheduled Downtimes
  • GETTING STARTED
    • Onboarding Checklist
    • User Journey
    • Create Singpass Application
    • Start Integration - Demo App
  • Technical Specifications
    • Singpass Authentication API
      • Overview of Singpass Flow
      • 1. Authorization Endpoint
        • Redirection on success
        • For Mobile Developers
      • 2. Token Endpoint
        • Authorization Code Grant
        • Client JWK Requirements
      • 3. Userinfo Endpoint
        • Requesting Userinfo
        • Validating the payload
      • .well-known Endpoints
        • OpenID Discovery Endpoint
        • JWKS Endpoint
      • Error Response
      • Frame busting for web views
    • Staging and Production URLs
  • Singpass Developer Portal (SDP)
    • User Guide
      • Obtain Access to SDP
      • Login to SDP
      • Toggle Staging vs Production
      • Create Staging App
      • Edit Staging App
      • Create Staging Test Account
      • Create Production App
      • Edit Production App
      • Consent to Singpass Service Agreement
      • View Singpass Service Agreement
      • Updating Billing Contact Information
      • Deactivate Production App
      • Activate Production App
      • How to View Production App Transactions
    • Understanding the App Config Fields
      • App Name
      • App Description
      • Site URL
      • Support Emails
      • Allowed Scopes
      • Redirect URL
      • Token-based Authentication
  • Data Catalog (Myinfo)
    • Understanding the Data
      • Local Registered Birth Records and Sponsored Child Records
      • CPF Contribution History (up to 15 months)
      • Notice of Assessment (Basic)
      • Notice of Assessment (Detailed)
    • Catalog
      • Personal
      • Finance
      • Education and Employment
      • Family
      • Vehicle and Driving Licence
      • Property
      • Government Scheme
  • TESTING
    • Testing with Singpass App
    • Myinfo Test Personas
  • MORE INFORMATION
    • Contact
Powered by GitBook
On this page
  • Step 1: Setup Token Endpoint Code
  • Step 2: Update request parameters
  • Step 3: Call the Token Endpoint and Test
  • Step 4: Decode the ID Token

Was this helpful?

  1. GETTING STARTED
  2. Custom Integration

Invoke Token Endpoint

Was this helpful?

After successful authentication, Singpass will return an authorization code and state back to your application redirect URL as described under the section

The value of the code parameter received will be used to call the Singpass token endpoint to exchange for the security tokens (i.e. ID token and access token) per the authorization flow.

Token endpoint for different environments:

Staging
Production

Token Endpoint can only be called from server-side code.

Step 1: Setup Token Endpoint Code

  • Create a new server-side code file called api.js and copy the logic needed for login from the code panel. This file contains the function to generate the client assertion as explained in section . For the demo Singpass application, the api.js file is within the Singpassdemoappserver code.

Do not use the sample private key as it has been compromised. For demo purposes, all keys have been displayed but It is recommended not to directly store your private key/ public key in your code, you should follow the best practices to store them in either Database or Secure Storage, or perhaps store them as environment variables, or in a config files, should avoid hardcoding.

//Api.js

exports.handler = async (event) => {
      try {   
        const REACT_APP_SIGNATURE_PRIVATE_KEY = {
          kty: "EC",
          d: "0GlHbGc8vSnyiB-Lf4_im_WFwrxM0MJjkk96o1-K3JQ",
          crv: "P-256",
          x: "wg11s6ZpBc0my5gT-mYatTZRDhgStyd_0qARVBwAWa4",
          y: "hlVoYWwlCuTMnm79Ppmf3RslIwDRhqdCCnm01PkhA2s"
        };
        const REACT_APP_ENCRYPTION_PRIVATE_KEY =  {
          kty: "EC",
          d: "p4YZHS0_BS4VMUayEtt38qi2sMdkhs4JRFlks7HJCD8",
          crv: "P-256",
          x: "0GR5oBa1FINjCZP_W-nR8Yqoz4E_9j7lgCuRPh9PZTA",
          y: "0leGfxdQSJdtubopqhj5uhPVYV3LSd_yf3y2DdRD5No"
        }
    REACT_APP_CLIENT_ID="tLRDBkf1CNy5Rsi34mEKuOD5EpQAwjIq"
    REACT_APP_JWTTOKENURL="https://stg-id.singpass.gov.sg"
    REACT_APP_SPTOKENURL="https://stg-id.singpass.gov.sg/token"
    REACT_APP_KID="testing123"
    REACT_APP_REDIRECT_URI="https://singpassdemoapp.netlify.app/callback"     
       const code =event.queryStringParameters.code;
        const jose = require("jose");
        const moment = require("moment");
        const axios = require("axios");
        const alg = "ES256";
        //Signature Keys
        const privateKey = await jose.importJWK(REACT_APP_SIGNATURE_PRIVATE_KEY, alg);
        const nowTime = moment().unix();
        const futureTime = moment().add(2, "minutes").unix();
        const jwt = await new jose.SignJWT({
          sub: REACT_APP_CLIENT_ID,
          iss: REACT_APP_CLIENT_ID,
          aud: REACT_APP_JWTTOKENURL,
          iat: nowTime,
          exp: futureTime,
        })
          .setProtectedHeader({
            alg: "ES256",
            kid: REACT_APP_KID,
            typ: "JWT",
          })
          .sign(privateKey);
       const url = REACT_APP_SPTOKENURL;
        const { data } = await axios.post(
          url,
          new URLSearchParams({
            client_id: REACT_APP_CLIENT_ID,
            redirect_uri: REACT_APP_REDIRECT_URI,
            code: code,
            client_assertion_type:
              "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            grant_type: "authorization_code",
            client_assertion: jwt,
          }),
          {
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
            },
          }
        );
      
        //Enc Keys (only for agency use where profile is direct-pii)
       try {
          const descprivateKey = {
            kty: "EC",
            d: "p4YZHS0_BS4VMUayEtt38qi2sMdkhs4JRFlks7HJCD8",
            crv: "P-256",
            x: "0GR5oBa1FINjCZP_W-nR8Yqoz4E_9j7lgCuRPh9PZTA",
            y: "0leGfxdQSJdtubopqhj5uhPVYV3LSd_yf3y2DdRD5No",
          };
    
          const privateKey2 = await jose.importJWK(
            descprivateKey,
            "ECDH-ES+A256KW"
          );
          const { plaintext } = await jose.compactDecrypt(
            data.id_token,
            privateKey2
          );
          const dto = new TextDecoder().decode(plaintext);
          const result = await jose.decodeJwt(dto);
          const NRIC = result.sub.substring(2, 11);
          const UUID= result.sub.substring(14);
          //Return NRIC
          return {
            statusCode: 200,
            body: JSON.stringify({ data: NRIC , UUID: UUID}),
            headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Allow-Methods": "GET, POST, OPTION"
            },
          };
          //Return error
        } catch (e) {
          console.log(e);
        } 
      } catch (e) {
        if (e.response?.data) {
          console.log(e.response.data);
          }
       return {
         statusCode: 500,
          body: JSON.stringify({ data: e }),
          headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Allow-Methods": "GET, POST, OPTION"
          }, 
        }; 

      }
    
  
    
  }

Step 2: Update request parameters

  • Update request parameters accordingly for the token endpoint with the following attributes:

Key
Description

client_id

redirect_uri

grant_type

The type of grant being requested. This must be set to authorization_code

code

The code issued earlier in the auth session

client_assertion_type

This MUST be set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer

client_assertion

Create attributes for all the request parameters and update them accordingly. For the demo singpass application, update all the following fields within the api.js file.

     const REACT_APP_SIGNATURE_PRIVATE_KEY = {
          kty: "EC",
          d: "0GlHbGc8vSnyiB-Lf4_im_WFwrxM0MJjkk96o1-K3JQ",
          crv: "P-256",
          x: "wg11s6ZpBc0my5gT-mYatTZRDhgStyd_0qARVBwAWa4",
          y: "hlVoYWwlCuTMnm79Ppmf3RslIwDRhqdCCnm01PkhA2s"
        }; 
         //Update to the signature JWT private key
        const REACT_APP_ENCRYPTION_PRIVATE_KEY =  {
          kty: "EC",
          d: "p4YZHS0_BS4VMUayEtt38qi2sMdkhs4JRFlks7HJCD8",
          crv: "P-256",
          x: "0GR5oBa1FINjCZP_W-nR8Yqoz4E_9j7lgCuRPh9PZTA",
          y: "0leGfxdQSJdtubopqhj5uhPVYV3LSd_yf3y2DdRD5No"
        }
        //Update to the encryption JWT private key. Mandatory only if profile type is direct-pii-allowed
    const REACT_APP_CLIENT_ID="tLRDBkf1CNy5Rsi34mEKuOD5EpQAwjIq"
//Update to the ClientID obtain from Singpass Developer Portal 
    const REACT_APP_JWTTOKENURL="https://stg-id.singpass.gov.sg"
//Default to staging JWT endpoint
    const REACT_APP_SPTOKENURL="https://stg-id.singpass.gov.sg/token"
//Default to staging token endpoint
    const REACT_APP_KID="testing123"
//Update to the signature JWT kid key ID
    const REACT_APP_REDIRECT_URI="https://singpassdemoapp.netlify.app/callback" 
//Update to the Redirect/Callback URL indicated in Singpass Developer Portal 

Step 3: Call the Token Endpoint and Test

  • After configuring the token request parameter, and calling the server function through a postman call by providing the code retrieved from the authorization endpoint response, upon successful authorization, your application will be responded to with an ID token and access token like the following example.

{
  "access_token" : "XAvWII9OKTwB1GInRMNi+H3cXb0/FHHqHQ+ks2TV1SU=",
  "token_type" : "Bearer",
  "id_token" : "eyJraWQiOiJuZGlfc3RnXzAxIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJzPVM4ODI5MzE0Qix1PTFjMGNlZTM4LTNhOGYtNGY4YS04M2JjLTdhMGU0YzU5ZDZhOSIsImF1ZCI6InVQQVh2RFphZzR2STRMWlp2U0pIaUFndHNBekFFZ3NmIiwiYW1yIjpbInB3ZCIsInN3ayJdLCJpc3MiOiJodHRwczovL3N0Zy1pZC5zaW5ncGFzcy5nb3Yuc2ciLCJleHAiOjE3MTU2NzM1OTYsImlhdCI6MTcxNTY3Mjk5Niwibm9uY2UiOiJGZEJYOVRHSi9qdGRUTEhOYlV0ckRla1FRY2RNRWZtRFlhRVU4eGZtVWlFPSJ9.ppxiB9S4R3v_kDYOpgkIG8hd3D2AH1UP3K1Gc243MF4I6lSQTANVHZ84RTYSRYatDSac2mkuw81EOBNSqgAnsw"
}

Token Endpoint Response Fields

Key
Description

access_token

A random string that isn’t used.

token_type

The type of token being requested, Bearer only so far.

id_token

Step 4: Decode the ID Token

  • After receiving the ID token, your application needs to decrypt the ID token with your encryption JWT private key to obtain the NRIC/UUID. For the demo singpass application, the following code works as the decrypt function.

//Api.js
{
          const privateKey2 = await jose.importJWK(
            REACT_APP_ENCRYPTION_PRIVATE_KEY,
            "ECDH-ES+A256KW"
          );
          const { plaintext } = await jose.compactDecrypt(
            data.id_token,
            privateKey2
          );
          const dto = new TextDecoder().decode(plaintext);
          const result = await jose.decodeJwt(dto);
          const NRIC = result.sub.substring(2, 11);
          const UUID= result.sub.substring(14);
          //Return NRIC
          return {
            statusCode: 200,
            body: JSON.stringify({ data: NRIC , UUID: UUID}),
            headers: {
              "Content-Type": "application/json",
              "Access-Control-Allow-Origin": "*",
              "Access-Control-Allow-Headers": "Content-Type",
              "Access-Control-Allow-Methods": "GET, POST, OPTION"
            },
          };

If you have received the response successfully, congratulations you have successfully integrated with Singpass.

This should be client_id of the registered client provided in the for each Application

The redirect URL being used in this auth session. The value will be validated against the list of redirect URIs that were pre-configured in .

A JWT identifying the client as setup in the

The ID token with relevant claims in JWT format signed by the Singpass. Note that the example response body shows a JWS (3-part structure separated by dots), but the format will differ for a JWS in JWE (5-part structure). Refer for more details about the ID token structure.

Generally follows OIDC error response specifications. For more information, please refer to .

Invoke Authorization Endpoint.
Setup Client Assertion
Token Error Response specifications
Application configuration
Application Configuration
https://stg-id.singpass.gov.sg/token
https://id.singpass.gov.sg/token
previous step
here