---
title: "Auth0 Integration with Singpass"
description: "Learn how to enable Singpass login with QR code in Auth0 Universal Login"
authors:
  - name: "Amin Abbaspour"
    url: "https://auth0.com/blog/authors/amin-abbaspour/"
  - name: "Vikas Jayaram"
    url: "https://auth0.com/blog/authors/vikas-jayaram/"
date: "Nov 10, 2021"
category: "Developers,Tutorial,Singpass"
tags: ["singpass", "authentication", "qr"]
url: "https://auth0.com/blog/auth0-integration-with-singpass/"
---

# Auth0 Integration with Singpass

When it comes to national digital identity, Singapore has a lot to say. 
Singapore launched [Singpass](https://www.singpass.gov.sg/) in 2003 and has been enhancing it over the years. 
Singpass’s initial goal was to authenticate citizens to government agencies and has since expanded to use in the private sector quite successfully.
Today, Singapore residents use Singpass to securely access over 1,700 services from more than 460 government agencies and private sector organisations. 

With Auth0's customer base quickly growing in Southeast Asia, Singapore and Japan, it was about time to officially offer Singpass services on the Auth0 platform.

This is the first of a two-post series. In this post, we’ll learn how to integrate Singpass QR Login with Auth0 Universal Login. 
In the next post, we’ll look into integrating Myinfo to share selective personal data such as name and email address.

## How Singpass QR Login Works

[Singpass QR login](https://api.singpass.gov.sg/library/login/developers/overview-at-a-glance) uses OIDC authorization code flow. 
Singpass offers an embedded SDK that renders a QR code returned from the authorization endpoint.

```html
<script src=”https://id.singpass.gov.sg/static/ndi_embedded_auth.js”></script>
```

Websites integrate with Singpass by hosting NDI embedded Javascript SDK. On page load, the Singpass SDK starts an authorization flow, with the `state` and `nonce` loaded to the page from the backend.

```javascript
const authParamsSupplier = async () => {
      return { state: "my-state", nonce: "my-nonce"};
  };

const onError = (errorId, message) => {
    console.log(`Error. errorId:${errorId} message:${message}`);
};

const initAuthSessionResponse = window.NDI.initAuthSession(
    'ndi-qr',
    {
        clientId: SINGPASS_CLIENT_ID, 
        redirectUri: SINGPASS_REDIRECT_URL,
        scope: 'openid',
        responseType: 'code'
    },
    authParamsSupplier,
    onError
);
```

NDI clients call authorization endpoint to return an embedded QR code. The Singpass app then scans the generated QR code, and the user then confirms the login. 
Next, the Embedded SDK receives a confirmation over Websocket and redirects to the client’s callback URL with an authorization code and state. 

![Singpass sequence diagram](https://images.ctfassets.net/23aumh6u8s0i/vfNDWisx8csQ7Ifg777EQ/7037d43c9924c70239e6d49fd365c988/01_private-qr.png)

After the exchange, `id_token`’s `sub` claim contains either an opaque digital identifier known as UUID or a combination of UUID and NRIC. 

```json
{ 
   "sub" : "s=S8829314B,u=1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9",
   "aud" : "xxNsTfleQMHoW6tbUgSVNwnLWQ0xTeV0",
   "iss" : "https://stg-id.singpass.gov.sg",
   "exp" : 1609907975,
   "iat" : 1609907375,
   "nonce" : "alh5DS2Gfndv9i0jXYViqGIhiQdP4+4BrUvBhDXBYKk=",
   "amr" : [ "pwd", "swk" ]
}
```

Depending on the information requested, `id_token` is either signed or encrypted.

![id_token](https://images.ctfassets.net/23aumh6u8s0i/1IrIEcWP6iAmJQ4pS7iNrp/e0cb15a685523ab581f7567c543411b8/Screen_Shot_2021-10-13_at_13.12.10.png)

## Integrate Singpass with Auth0
During the discovery phase, we noticed some gaps between what Auth0 offers for integration with external OIDC providers and what Singpass required.

First challenge was that the `nonce` parameter that applications send to Auth0 is not available within UL where we wanted to initiate the NDI SDK.

Second, Auth0 currently does not support the [client-assertion](https://datatracker.ietf.org/doc/html/rfc7523#section-2.2) for token endpoints of OIDC connections. 
Singpass expects clients to use asymmetric [client_assertion JWT](https://stg-id.singpass.gov.sg/docs/authorization/api#_client_jwk_requirements) and customers should follow NDI’s process to generate an Elliptic-curve keypair and host public key in a public-facing `jwks.json` file. 
Customer’s public-key URL is shared with Singpass during lodging the application.

Lastly, Singpass’s `id_token` signing signature (`id_token_signing_alg_values_supported`), is `ES256` as pointed in their [openid-configuration](https://id.singpass.gov.sg/.well-known/openid-configuration). 
Auth0 only supports `RS256` signed `id_tokens` from upstream connections.

While integrating Singpass with Auth0 is not a native feature, we hope to build a simpler and easier solution in the future.

<include src="ebook-ads/Oauth2OidcGuide" />

### Technical Details

Our field team decided to challenge themselves by taking available tools within the Auth0 extensibility platform and develop an integration path for Singpass on Auth0.

We decided to proxy both authorization and token endpoints. The proxied version adds missing features of appending nonce, performing exchange with client_assertion, and validating the `ES256` signature in return.

![Singpass component diagram](https://images.ctfassets.net/23aumh6u8s0i/4IpSoCXSxRbyByzRnXk8jl/2d9c90bba361a99b9426ffbed16c1e20/singpass-seq.png)

1. User visits an application that requires authentication against Auth0
2. Auth0 detects no session and redirects to Universal Login page for interactive authentication
3. User selects login with Singpass from available logon options
4. NDI library inside UL page initiates connection with Singpass, fetches QR code, and displays it
5. User opens up Singpass app and scans the QR code
6. Singpass app communicates to Singpass authorization server in back-channel and accepts logon
7. Singpass redirects a callback to Auth0’s/login/callback endpoint using the front channel from the NDI SDK on the UL page
8. Auth0 connects to the proxy /token endpoint to exchange authorization code. Token endpoint validates client credentials
9. Proxy endpoint creates private_jwt client assertion and exchanges authorization code, validates response and returns results to Auth0
10. Auth0 decodes returning `id_token` from Singpass and issues its own `id_token` to the application, completing the sign-in flow

We developed four open-source versions of proxy endpoint; 
[hosted on Auth0](https://github.com/auth0-extensions/auth0-singpass-extension), [AWS Lambda](https://github.com/auth0-blog/auth0-singpass/tree/main/aws), [Cloudflare Workers](https://github.com/auth0-blog/auth0-singpass/tree/main/cloudflare), and vanilla [Express.js](https://github.com/auth0-blog/auth0-singpass/tree/main/express). 
In the rest of this article, we will focus on the configuration of the Singpass integration proxy inside Auth0. For production use, we suggest hosting proxy endpoints in your environment.

### Deploying Singpass Integration Proxy
First, create a SPA companion app with Allowed Callback URLs set to "https://your-custom-domain/login/callback". 

Then head to Extensions, click on "Create Extension" and enter GitHub URL https://github.com/auth0-extensions/auth0-singpass-extension. During installation populate with the correct values as follows:

![Extension settings](https://images.ctfassets.net/23aumh6u8s0i/1YGwCulxPHWHiH9OPImMvC/f361a7bb82c2a1baaf0110160272a4da/03_singpass-extension-settings.jpg)

Values are:

* **Auth0 custom domain** <br>
  This is your Auth0 custom domain name. You need to have a custom domain name enabled in Auth0
* **Auth0 client id** <br> 
  `client_id` of companion app in Auth0
* **Auth0 client secret** <br>
  `client_secret` of companion app in Auth0
* **Singpass environment** <br>
  Is set to Staging only. 
* **Singpass client id** <br>
  `client_id` assigned to you from Singpass
* **Singpass signing alg** <br>
  Always `ES256`
* **Relying on party jwks endpoint** <br>
  This is your JWKS endpoint used during Singpass onboarding registration
* **Relying on party private key** <br>
  Your Elliptic-curve private key used during Singpass onboarding registration. This is not required if the proxy endpoint is running in your own infrastructure, which is the preferred deployment method for production
* **Relying on party kid** <br>
  This is your JWKS key ID used during Singpass onboarding registration

Once installed, you’ll need to Singpass connection under Authentication > Social > Create Connection > Create Custom. Use client_id and secret from companion app and populate URLs depending on your tenant region and node version as documented [here](https://github.com/auth0-extensions/auth0-singpass-extension#usage). Fetch User Profile script template is also available in [Github repo](https://github.com/auth0-blog/auth0-singpass/blob/main/oauth2-connection-config/fetchUserProfile.js). Ultimately [enable PKCE](https://github.com/auth0-blog/auth0-singpass/blob/main/enable-pkce.sh) on this connection.

![singpass social connection](https://images.ctfassets.net/23aumh6u8s0i/2h8ucNIa0Pns4bzRZMnJSe/9bb19078adc8e67a43f224d3ca6a3ffe/04_singpass-connection.jpg)

Then enable this connection to applications that require sign-in with Singpass:

![singpass application connection](https://images.ctfassets.net/23aumh6u8s0i/2Drur5RknH2jD9KEWQVSsJ/5eee8e828e78fa107c495e1ff3017a27/singpass-demo-app.png)

### Universal Login
Next, configure Universal Login to host NDI SDK and render QR login code when requested. 
Head to Branding > Universal Login > Login. Enable “Customize Login Page” and configure page HTML.  

```html
    <script src="https://cdn.auth0.com/js/lock/11.30/lock.min.js"></script>
    <!-- ADDED for Singpass -->
    <script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
    <script src="https://stg-id.singpass.gov.sg/static/ndi_embedded_auth.js"></script>
    <!-- /ADDED -->
```

Add `authButtons` under `theme` object of Lock configuration:

```javascript
var lock = new Auth0Lock(config.clientID, config.auth0Domain, {
    // ...
    theme: {
      authButtons: {
        "singpass": {
          displayName: "Singpass",
          primaryColor: "#cf0b15",
          foregroundColor: "#FFFFFF",
          icon: "https://app.singpass.gov.sg/apple-touch-icon.png"
        }
      }
    }
  });
```

And logic to render QR code as following:

```javascript
lock.once('signin ready', function () {
  console.log('siginin ready');
  if (config.extraParams.singpass) {
    $(".auth0-lock.auth0-lock").removeProp("box-sizing");
    var connectionConfig = initConnectionConfig();
    addEnterpriseConnections(connectionConfig);
    init();
  }
});

lock.show();

function initConnectionConfig() {
  return {
    general: {
      backButton: '<span class="auth0-lock-back-button"><svg focusable="false" enable-background="new 0 0 24 24" version="1.0" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <polyline fill="none" points="12.5,21 3.5,12 12.5,3 " stroke="#000000" stroke-miterlimit="10" stroke-width="2"></polyline> <line fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" x1="22" x2="3.5" y1="12" y2="12"></line> </svg></span>',
      enterprise_panel: '<div id="ndi-qr" class="auth0-enterprise-button-content"></div>'
    }
  };
}

function addEnterpriseConnections(connectionConfig) {
  var backButton = $(connectionConfig.general.backButton);
  $('.auth0-lock-submit').hide();
  backButton.appendTo('.auth0-lock-header');
  backButton.on('click', function (e) {
    e.preventDefault();
    window.history.back();
  });
  var enterprisePanel = $(connectionConfig.general.enterprise_panel);
  enterprisePanel.appendTo('.auth0-lock-body-content');
  $('.auth0-lock-content').hide();
  enterprisePanel.show();
}

function init() {
  const authParamsSupplier = async () => {
    // Replace the below with an `await`ed call to initiate an auth session on your backend
    // which will generate state+nonce values, e.g
    return { state: decodeURIComponent(config.extraParams.ndi_state), nonce: decodeURIComponent(config.extraParams.ndi_nonce) };
  };

  const onError = (errorId, message) => {
    console.log(`onError. errorId:${errorId} message:${message}`);
  };

  const initAuthSessionResponse = window.NDI.initAuthSession(
          'ndi-qr',
          {
            clientId: SINGPASS_CLIENT_ID, // Replace with your Singpass client ID
            redirectUri: SINGPASS_AUTH0_CALLBACK,        // Replace with your Auth0 custom domain
            scope: 'openid',
            responseType: 'code'
          },
          authParamsSupplier,
          onError,
          {
            renderDownloadLink: false,
            appLaunchUrl: '' // Replace with your iOS/Android App Link
          },
  );

  console.log('initAuthSession: ', initAuthSessionResponse);
}
```

You can find the full version of the custom Universal Login page [here](https://github.com/auth0-blog/auth0-singpass/tree/main/universal-login).

### How it Looks in Action

You should be able to see Log in with Singpass as connections options on the login page.

![singpass login selection](https://images.ctfassets.net/23aumh6u8s0i/1qhC2Zg65ZshMGMEgXVmzs/a3c692bb512777eca5ee93dde07c3285/lock-singpass-p1v3.png)

Click on "Log in with Singpass. QR code renders.
![singpass QR in lock](https://images.ctfassets.net/23aumh6u8s0i/5cIFPyXvsCJ0u4iwrOWaIh/dc1aec1d8421154d375222f0e3cc1c4d/lock-singpass-qrv2.png)

Scan the QR code with the Singpass mobile app.

![app combined](https://images.ctfassets.net/23aumh6u8s0i/6pGbrMObvL33JzKFWnkJ1Y/4de633516efb24f40220ab0a2a513b0b/singpass-app-combined.png)

### User Profile
We’re taking Singpass UUID to form Auth0 `user_id`. That logic is sitting in the Fetch User Profile script of the Singpass connection.

![singpass user profile](https://images.ctfassets.net/23aumh6u8s0i/glYisSzA8BDvvAmOJfAyV/3bcc4833cce7954c922efe336ca6ddad/14_singpass-user-profile.png)

In the next article, we will enable Myinfo, where you will fetch email and name information and use it to link Singpass users with existing customers with matching details. Stay tuned!

## Login experience in Action

> Note: This video is captured with staging app (v13.0.0-stg) in the staging environment. The actual app interface might be different.


<include src="Vidyard" id="nrB87krJhPFEAyd4giqY6H" />

<include src="asides/AboutAuth0" />