# Next.js (client-side rendering)

This page provides a step-by-step guide on how to integrate the TypeScript SDK in a [Next.js](https://nextjs.org/) 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.

<figure><img src="https://2214909052-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpBW92htBuXTrDYoKovQ2%2Fuploads%2FKzsUAC4m4cUq9rJ1FQUc%2Fimage.png?alt=media&#x26;token=73750d0e-cda9-4e9a-aaa1-3906164944f9" alt="" width="375"><figcaption><p>sgID login page with an ice cream flavour selector</p></figcaption></figure>

{% hint style="warning" %}
If you have not already obtained your client credentials via registration, please [register your client](https://docs.id.gov.sg/introduction/getting-started/register-your-application) 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
{% endhint %}

## Running the example locally

### Step 1: Clone the repo

To run the example locally, clone from our [source code](https://github.com/opengovsg/sgid-client/tree/develop/examples/nextjs-csr) by running:

```bash
git clone https://github.com/opengovsg/sgid-client.git
cd sgid-client/examples/nextjs-csr
cat .env.example > .env # Copy the `.env.example` file
npm install
```

### Step 2: Update your environment variables

Update your `.env` file with your client credentials.

{% code title="examples/nextjs-csr/.env" %}

```
SGID_CLIENT_ID=<your client id>
SGID_CLIENT_SECRET=<your client secret>
SGID_PRIVATE_KEY=<your private key>
```

{% endcode %}

### Step 3: Run the example

```bash
# In the /sgid-client/examples/nextjs-csr directory
npm run dev
```

Visit <http://localhost:5001> to check that your app is running.

If you click on 'Login with Singpass' and authenticate with your Singpass mobile app, you should see your user info on the success screen.

## Breaking the example down

In this section, we'll break down the different steps that our example app goes through.

1. [Initialize the SDK](#step-1-initialize-the-sdk)
2. [Initialize an in-memory store for sessions](#step-2-initialize-an-in-memory-store-for-sessions)
3. [Create the `/api/auth-url` endpoint](#step-3-create-the-api-auth-url-endpoint)
4. [Create the`/api/redirect` endpoint](#step-4-create-the-api-redirect-endpoint)
5. [Create the`/api/userinfo` endpoint](#step-5-create-the-api-userinfo-endpoint)
6. [Create a button to redirect to `/api/auth-url`](#step-6-create-a-button-to-redirect-to-api-auth-url)
7. [Create a `/logged-in` page to fetch and render user info](#step-7-create-a-logged-in-page-to-fetch-and-render-user-info)

### Step 1:  Initialize the SDK

In this step, we will create an instance of our `SgidClient` class which will help us to interface with the sgID server.

In the `.env` file created from the previous step, fill out your sgID credentials.

{% code title="examples/nextjs-csr/.env" %}

```
SGID_CLIENT_ID=<your client id>
SGID_CLIENT_SECRET=<your client secret>
SGID_PRIVATE_KEY=<your private key>
```

{% endcode %}

{% hint style="info" %}
The main idea here is to load your sgID credentials in a secure way using environment variables instead of hard-coding them into your app.
{% endhint %}

Next, initialize the SDK by calling the constructor and providing your client credentials.

{% code title="src/lib/sgidClient.ts" %}

```typescript
// Server-side code
import { SgidClient } from '@opengovsg/sgid-client'

const sgidClient = new SgidClient({
  clientId: String(process.env.SGID_CLIENT_ID),
  clientSecret: String(process.env.SGID_CLIENT_SECRET),
  privateKey: String(process.env.SGID_PRIVATE_KEY),
  redirectUri: 'http://localhost:5001/api/redirect',
})

export { sgidClient }
```

{% endcode %}

### 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.&#x20;

{% hint style="info" %}
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.
{% endhint %}

{% code title="src/lib/store.ts" %}

```typescript
/**
 * 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
}

type Session = {
  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 = new Map<string, Session>()
} else {
  // If the store does not exist, initialize it
  if (!global.store) {
    global.store = new Map<string, Session>()
  }
  store = global.store
}

export { store }
```

{% endcode %}

### 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.

<figure><img src="https://2214909052-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpBW92htBuXTrDYoKovQ2%2Fuploads%2Fp17CgPCG1EPDgaiVDK81%2Fimage.png?alt=media&#x26;token=f6161986-3837-4f6b-89b0-4e019d1c5787" alt="" width="375"><figcaption><p>Clicking 'Login with Singpass app' redirects the browser to the /api/auth-url endpoint</p></figcaption></figure>

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

{% code title="pages/api/auth-url.ts" lineNumbers="true" %}

```typescript
// Server-side code
import type { 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";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  let { state } = req.query;
  state = String(state);

  // Generate a session ID
  const sessionId = uuidv4();

  // Generate a PKCE pair
  const { codeChallenge, codeVerifier } = generatePkcePair();

  // Generate an authorization URL
  const { url, nonce } = sgidClient.authorizationUrl({
    state,
    codeChallenge,
  });

  // Store the code verifier, state, and nonce
  store.set(sessionId, { state, nonce, codeVerifier });

  // Set the sessionID in the browser's cookies
  setCookie("sessionId", sessionId, { req, res });

  // Redirect the browser to the authorization URL
  res.redirect(url);
}
```

{% endcode %}

### 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

```
http://localhost:5001/api/redirect?
    code=someAuthCode
    &state=someState
```

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)

{% code title="pages/api/redirect.ts" %}

```typescript
// Server-side code
import type { NextApiRequest, NextApiResponse } from 'next'
import { store } from '../../lib/store'
import { sgidClient } from '../../lib/sgidClient'
import { getCookie, setCookie } from 'cookies-next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  // Retrieve the authorization code from query params
  let { code, state } = req.query
  
  // Retrieve the session ID from browser cookies
  const sessionId = getCookie('sessionId', { req, res })

  // Validating that the sessionID, contents in session, and auth code is present
  if (typeof sessionId !== 'string') {
    return res.status(401).send('Session ID not found in browser cookies')
  } else if (!code) {
    return res.status(400).send('Authorization code not found in query params')
  }
  code = String(code)

  const session = store.get(sessionId)

  if (!session) {
    return res.status(401).send('Session not found')
  } else if (state && state !== session.state) {
    return res.status(400).send('State does not match')
  }

  const { nonce, codeVerifier } = session

  if (!codeVerifier || typeof codeVerifier !== 'string') {
    return res.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 server
  const { accessToken, sub } = await sgidClient.callback({
    code,
    nonce,
    codeVerifier,
  })

  // Store the access token and sub
  const updatedSession = {
    ...session,
    accessToken,
    sub,
  }
  store.set(sessionId, updatedSession)

  res.redirect('/logged-in')
}
```

{% endcode %}

{% hint style="info" %}
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.
{% endhint %}

### 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

{% code title="pages/api/userinfo.ts" lineNumbers="true" %}

```typescript
// Server-side code
import type { NextApiRequest, NextApiResponse } from 'next'
import { store } from '../../lib/store'
import { sgidClient } from '../../lib/sgidClient'
import { getCookie } from 'cookies-next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  // Retrieve the session ID from browser cookies
  const sessionId = getCookie('sessionId', { req, res })

  if (typeof sessionId !== 'string') {
    return res.status(401).send('Session ID not found in browser cookies')
  }

  // Retrieve the access token from memory using the session ID
  const session = store.get(sessionId)

  if (!session) {
    return res.status(401).send('Session not found')
  }
  const { accessToken, sub } = session

  if (!accessToken || typeof accessToken !== 'string') {
    return res.status(400).send('Access token not in session')
  } else if (!sub || typeof sub !== 'string') {
    return res.status(400).send('Sub not in session')
  }

  // Request user info using the access token
  const { data } = await sgidClient.userinfo({ accessToken, sub })

  // Store user info in the in-memory store
  const updatedSession = {
    ...session,
    userInfo: data,
  }
  store.set(sessionId, updatedSession)

  const { accessToken: _, nonce: __, ...dataToReturn } = updatedSession

  // Return the user info
  res.json(dataToReturn)
}
```

{% endcode %}

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.&#x20;

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](https://github.com/opengovsg/sgid-client/tree/feat/example-next-csr/examples/nextjs-csr).

{% code title="pages/index.tsx" lineNumbers="true" %}

```typescript
// Client-side code
import type { NextPage } from "next";
import { useState } from "react";
import Link from "next/link";

const flavours = ["Vanilla", "Chocolate", "Strawberry"] as const;
type IceCream = (typeof flavours)[number];

const Home: NextPage = () => {
  const [state, setState] = useState<IceCream>("Vanilla");

  return (
     <div>
        <h2>Favourite ice cream flavour</h2>
        <div>
          {flavours.map((flavour) => (
            <div
              key={flavour}
              onClick={() => setState(flavour)}
            >
              <input
                type="radio"
                checked={state === flavour}
                value={flavour}
                onChange={(e) => setState(e.target.value as IceCream)}
                title="flavour"
              />
              {flavour}
            </div>
          ))}
        </div>

        <Link
          prefetch={false}
          href={`/api/auth-url?state=${state}`}
        >
          <button>
            Login with Singpass app
          </button>
        </Link>
      </div>
  );
};

export default Home;
```

{% endcode %}

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&#x20;

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.

{% code title="pages/logged-in.tsx" lineNumbers="true" %}

```typescript
// Client-side code
import { useEffect, useState } from "react";

type UserInfoRes = {
  sub?: string;
  userInfo?: Record<string, string>;
  state?: string;
};

const LoggedIn = () => {
  const [data, setData] = useState<UserInfoRes | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    const getUserInfo = async () => {
      try {
        setIsLoading(true);
        const res = await fetch("/api/userinfo", { credentials: "include" });
        const data = (await res.json()) as UserInfoRes;
        setData(data);
      } catch (error) {
        setError(error instanceof Error ? error.message : String(error));
      } finally {
        setIsLoading(false);
      }
    };
    getUserInfo();
  }, []);
  
  if (isLoading) {
    return <div>Loading...</div>;
  } else if (error) {
    return <div>{`Error: ${error}`}</div>;
  }

  return (
    <div>
      <div>User Info</div>
      
      {data?.sub ? (
        <div>{`sgID: ${data.sub}`}</div>
      ) : null}
      {Object.entries(data?.userInfo ?? {}).map(([field, value]) => (
        <div>{`${field}: ${value}`}</div>
      ))}
      {data?.state ? (
        <div>
          {`Favourite ice cream flavour: ${data.state}`}
        </div>
      ) : null}
    </div>
  );
};

export default LoggedIn;
```

{% endcode %}

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! :tada:

You have reached the end of the Next.js (CSR) step-by-step guide.&#x20;

The [source code](https://github.com/opengovsg/sgid-client/tree/develop/examples/nextjs-csr) 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.

If you want to find out more about how sgID works, click here to [**learn about the sgID protocol**](https://docs.id.gov.sg/learn-the-basics/protocols/sgid).&#x20;

If you have more questions about sgID, check out our [**FAQ**](https://docs.id.gov.sg/faq-developers) for answers to common questions.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.id.gov.sg/integrations-with-sgid/typescript-javascript/framework-guides/next.js-client-side-rendering.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
