This page provides a step-by-step guide on how to integrate the TypeScript SDK in a Next.js project using the pages router, api routes and client-side rendering (CSR).
To illustrate our example, we have prepared a demo app which will allow you to retrieve your user's name and favorite ice cream flavor after they log in with sgID.
If you have not already obtained your client credentials via registration, please register your client before proceeding.
For this example, you should add:
1. [openid, myinfo.name] as the scopes and
2. http://localhost:5001/api/redirect as a redirect URL
Running the example locally
Step 1: Clone the repo
To run the example locally, clone from our source code by running:
gitclonehttps://github.com/opengovsg/sgid-client.gitcdsgid-client/examples/nextjs-csrcat.env.example>.env# Copy the `.env.example` filenpminstall
Step 2: Update your environment variables
Update your .env file with your client credentials.
Step 2: Initialize an in-memory store for sessions
To maintain session data across requests, we will need to initialize an in-memory store for our server.
In a production environment, your session data should be stored in a database but for our example, we will be using an in-memory store to reduce complexity.
src/lib/store.ts
/** * We place the store in the global object to prevent it from being cleared whenever compilation happens */declare global {var store:Map<string,Session> |undefined}typeSession= { state?:string; nonce?:string; codeVerifier?:string; accessToken?:string; userInfo?:Record<string,string>; sub?:string;};let store:Map<string,Session>if (process.env.NODE_ENV==='production') { store =newMap<string,Session>()} else {// If the store does not exist, initialize itif (!global.store) {global.store =newMap<string,Session>() } store =global.store}export { store }
Step 3: Create the /api/auth-url endpoint
When an end user clicks on the sign in button on your application (e.g. 'Login with Singpass app'), it should redirect the browser to this endpoint.
The /api/auth-url endpoint should do the following
Generate a session ID
Generate a PKCE pair (consisting of code challenge and code verifier)
Generate an authorization URL
Store the code verifier in the in-memory store with the session ID as the key
Set the session ID in the browser's cookies
Redirect the browser to the authorization URL
pages/api/auth-url.ts
// Server-side codeimporttype { NextApiRequest, NextApiResponse } from"next";import { v4 as uuidv4 } from"uuid";import { store } from"../../lib/store";import { sgidClient } from"../../lib/sgidClient";import { setCookie } from"cookies-next";import { generatePkcePair } from"@opengovsg/sgid-client";exportdefaultfunctionhandler(req:NextApiRequest, res:NextApiResponse) {let { state } =req.query; state =String(state);// Generate a session IDconstsessionId=uuidv4();// Generate a PKCE pairconst { codeChallenge,codeVerifier } =generatePkcePair();// Generate an authorization URLconst { url,nonce } =sgidClient.authorizationUrl({ state, codeChallenge, });// Store the code verifier, state, and noncestore.set(sessionId, { state, nonce, codeVerifier });// Set the sessionID in the browser's cookiessetCookie("sessionId", sessionId, { req, res });// Redirect the browser to the authorization URLres.redirect(url);}
Step 4: Create the /api/redirect endpoint
After the user scans the QR code with their Singpass mobile app and authorizes your application to access the specified scopes, the sgID server will redirect the user's browser to the redirect_uri you specified earlier (either when initializing the SDK or when passed as a parameter to the authorizationUrl function).
The redirect will include the authorization code and the state (if provided earlier) in the form of query parameters. An example URL would look something like this
The /api/redirect endpoint should do the following
Retrieve the authorization code from query params, and the session ID from browser cookies
Retrieve the code verifier from the in-memory store
Exchange the authorization code and code verifier for the access token
Store the access token and sub in the in-memory store
Redirect the browser to a logged in page (or any page of your choice)
pages/api/redirect.ts
// Server-side codeimporttype { NextApiRequest, NextApiResponse } from'next'import { store } from'../../lib/store'import { sgidClient } from'../../lib/sgidClient'import { getCookie, setCookie } from'cookies-next'exportdefaultasyncfunctionhandler( req:NextApiRequest, res:NextApiResponse,) {// Retrieve the authorization code from query paramslet { code, state } =req.query// Retrieve the session ID from browser cookiesconstsessionId=getCookie('sessionId', { req, res })// Validating that the sessionID, contents in session, and auth code is presentif (typeof sessionId !=='string') {returnres.status(401).send('Session ID not found in browser cookies') } elseif (!code) {returnres.status(400).send('Authorization code not found in query params') } code =String(code)constsession=store.get(sessionId)if (!session) {returnres.status(401).send('Session not found') } elseif (state && state !==session.state) {returnres.status(400).send('State does not match') }const { nonce,codeVerifier } = sessionif (!codeVerifier ||typeof codeVerifier !=='string') {returnres.status(400).send('Code verifier not found') }// Exchange the auth code for the access token// At the end of this function, users are considered logged in by the sgID serverconst { accessToken,sub } =awaitsgidClient.callback({ code, nonce, codeVerifier, })// Store the access token and subconstupdatedSession= {...session, accessToken, sub, }store.set(sessionId, updatedSession)res.redirect('/logged-in')}
The sub is an end-user's unique sgID identifier.
If your application only needs to verify that a user is a real person with a Singpass account without needing to access any government-verified data, then you can stop here (after Step 4) and utilize the sub value to identify the user.
Step 5: Create the /api/userinfo endpoint
Once the browser has been redirected to a logged in/success page, your app can make a GET request to this endpoint which will use the access token stored in session to request user info from the sgID server.
The /api/userinfo endpoint should do the following:
Retrieve the session ID from browser cookies
Retrieve the access token from the in-memory store using the session ID
Request user info using the access token
Store user info in the in-memory store
Return the user info
pages/api/userinfo.ts
// Server-side codeimporttype { NextApiRequest, NextApiResponse } from'next'import { store } from'../../lib/store'import { sgidClient } from'../../lib/sgidClient'import { getCookie } from'cookies-next'exportdefaultasyncfunctionhandler( req:NextApiRequest, res:NextApiResponse,) {// Retrieve the session ID from browser cookiesconstsessionId=getCookie('sessionId', { req, res })if (typeof sessionId !=='string') {returnres.status(401).send('Session ID not found in browser cookies') }// Retrieve the access token from memory using the session IDconstsession=store.get(sessionId)if (!session) {returnres.status(401).send('Session not found') }const { accessToken,sub } = sessionif (!accessToken ||typeof accessToken !=='string') {returnres.status(400).send('Access token not in session') } elseif (!sub ||typeof sub !=='string') {returnres.status(400).send('Sub not in session') }// Request user info using the access tokenconst { data } =awaitsgidClient.userinfo({ accessToken, sub })// Store user info in the in-memory storeconstupdatedSession= {...session, userInfo: data, }store.set(sessionId, updatedSession)const { accessToken: _, nonce: __,...dataToReturn } = updatedSession// Return the user infores.json(dataToReturn)}
With this step, the API endpoints are completed. In the next 2 steps, we will complete the Next.js application by creating the user interface to interact with these endpoints.
Step 6: Create a button to redirect to /api/auth-url
Now, we need to create a button to redirect the browser to the /api/auth-url endpoint.
In the following examples, we will not include any styling in order to keep the code snippet short. If you would like to view and interact with an example with styling, feel free to refer to the source code.
Now when you run your Next.js app and visit http://localhost:5001, you should see a Login with Singpass button which when clicked should bring you to the sgID approval page with the QR code.
Step 7: Create a /logged-in page to fetch and render user info
After the /api/redirect endpoint completes the request and the user info is stored in the in-memory store, the browser will be redirected to the /logged-in page.
The redirection to this page also marks that the user has successfully logged in and your application should be able to freely retrieve user info from the in-memory store.
As such, we will make a GET request to the /api/userinfo endpoint on this page and render the user info. As with above, we will omit styling from the following example.
Now if you run your Next.js app, click on Login with Singpass and complete the authorization flow with your Singpass mobile app, you should be brought to this success page where you can view your personal details as well as your ice cream flavour selected on the login page.
Congratulations! 🎉
You have reached the end of the Next.js (CSR) step-by-step guide.
The source code for this example can be found here which includes a /api/logout API endpoint for logging out, styling with Tailwind CSS, and a README on how to run the example locally.