An open-source node.js package for SingPass and CorpPass

Open Government Products
Data.gov.sg Blog
Published in
4 min readSep 14, 2018

Engineering: Arshad Ali, Software Engineer
Copywriting: Alwyn Tan, Senior Software Engineer

We have recently implemented and released an npm package so that node.js applications can use SingPass and CorpPass for authentication. spcp-auth-client was created from code refactored out of FormSG, and released as open-source, so that other developers might freely benefit from it, and work together on improvements.

Background

A while ago, FormSG added a feature that allowed people to use SingPass to identify themselves when filling up forms. This has been incredibly helpful to ensure submitted responses are legitimate (examples include the Ministry of Manpower’s form for reporting workplace violations).

Given that CorpPass is almost identical, it made sense to re-use the code for SingPass to authenticate with CorpPass, so that we can identify submissions from companies. We took the opportunity to refactor the relevant part of the codebase, to de-couple the part that interacts with SingPass from the parts that depended on it. This would allow us to have both SingPass and CorpPass functionality implemented by a common module, invokable from wherever it is needed.

Outcome

We wound up with a small, neatly-contained module, which we christened spcp-auth-client. Further efforts separated it into its own repository, making it easier to provide specific tests without bloating the FormSG codebase. Bringing it in as a separate package also allowed us to slim down the list of direct dependencies for FormSG, since those have been moved to spcp-auth-client.

Benefits

Implementing spcp-auth-client was not a trivial exercise. We decided to open-source it so that others can freely use code that has received a fair amount of scrutiny to interact with SingPass and CorpPass, and make improvements as needed. Doing so also avoids wasting time spent writing code that is unrelated to the problems developers are hoping to address.

Usage

The following code snippets, adapted from our README, illustrates how spcp-auth-client is used to authenticate users with SingPass.

Create an SPCPAuthClient, along with your usual web framework (we use express in this example):

const SPCPAuthClient = require('@opengovsg/spcp-auth-client')
const express = require('express')
const client = new SPCPAuthClient({
partnerEntityId: '<your partner entity id>',
idpLoginURL: '<the SingPass url to redirect login attempts to>',
idpEndpoint: '<the SingPass url for authentication>',
esrvcID: '<the e-service id registered with SingPass>',
appCert: '<the public certificate issued to SingPass>',
appKey: '<the certificate private key>',
spcpCert: '<the public certificate of SingPass>',
extract: '<fn that extracts attributes from SingPass response>',
})

const app = express()

If a user is logging in, redirect to SingPass, specifying where the user should go after successful authentication:

const POST_LOGIN_PAGE = '/<target-url-after-login>'app.route('/login', (req, res) => {
const redirectURL = client.createRedirectURL(POST_LOGIN_PAGE)
res.status(200).send({ redirectURL })
})

SingPass would eventually hand control back to your application by sending a GET request to an endpoint agreed with you when you registered to use SingPass. Call client.getAttributes to complete the final step of authentication:

app.route('/assert', (req, res) => {
const { SAMLArt: samlArt, RelayState: relayState } = req.query
client.getAttributes(samlArt, relayState, (err, data) => {
// If all is well and login occurs, the attributes are given
const { attributes, relayState } = data
const { UserName: userName } = attributes
if (err) {
// Indicate that an error has occurred
res.cookie('login.error', err.message)
} else {
// Embed a session cookie, a JWT based on user name
const FOUR_HOURS = 4 * 60 * 60 * 1000
const jwt = client.createJWT({ userName }, FOUR_HOURS)
res.cookie('connect.sid', jwt)
}
res.redirect(relayState)
})
})

Use client.verifyJWT to subsequently verify if the user has logged in:

const isAuthenticated = (req, res, next) => {
client.verifyJWT(req.cookies['connect.sid'], (err, data) => {
if (err) {
res.status(400).send('Unauthorized')
} else {
req.userName = data.userName
next()
}
})
}
app.route(
'/protected-route',
isAuthenticated,
// ...
)

Where to get it

The package can be installed through npm:

The source code itself can be found on our GitHub:

We encourage people who work on applications which rely on SingPass and CorpPass to give this a go, and to file any issues or pull requests with us.

Afterword

As a side-note, there is a similar Ruby gem found on the general GovTech GitHub:

spcp-auth-client has mostly been the work of Arshad Ali, one of our software engineers. We would also like to thank the National Digital Identity team for their support in open-sourcing this library.

Note that you would need to register with your application with SingPass/CorpPass in order to use spcp-auth-client.

--

--

Open Government Products
Data.gov.sg Blog

We are Open Government Products, an experimental division of the Government Technology Agency of Singapore. We build technology for the public good.