SAML SSO: The Missing Piece in Your Next.js App's Authentication Puzzle 🧩
Tags: webdev, javascript, programming, tutorial
Published: 2023-09-19 12:46:06

_Find out how to complete the picture for a seamless login_

## Table of Contents
- [TL;DR](#tldr)

- [A Brief Description of SSO](#a-brief-description-of-sso)

- [Authentication vs Authorization](#authentication-vs-authorization)

- [Integrating SAML SSO into Your App](#integrating-saml-sso-into-your-app)

- [Install SAML Jackson](#install-saml-jackson)

- [Setup SAML Jackson](#setup-saml-jackson)

- [NextAuth.js Integration](#nextauthjs-integration)

- [Making the Authentication Request](#making-the-authentication-request)

- [Receiving the SAML Response](#receiving-the-saml-response)

- [Requesting the Access Token](#requesting-the-access-token)

- [Fetching the User Profile](#fetching-the-user-profile)

- [Authenticating the User](#authenticating-the-user)

- [Starts OAuth Sign In Flow](#starts-oauth-sign-in-flow)

- [Congratulations!](#congratulations)

- [Let’s Take a Moment and Review What We've Learned](#lets-take-a-moment-and-review-what-weve-learned)

- [Community](#community)

## TL;DR: 


By the end of this tutorial, you'll have a fully functional SAML SSO integration for your Next.js app. 🚀

This will be the process: 

* Walk through the process step by step, complete with code examples.


![cat with sunglasses](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sebr1ejsng2wetbduqd.gif)


Before you make it too far, a good place to start is to find out your use case.

If you're looking to enhance the security and user experience of your Next.js app by implementing SAML Single Sign-On (SSO) authentication for your customers, you’ve landed in the right place. Let’s get started!

### A Brief Description of SSO
Single Sign-On (SSO) simplifies user authentication by allowing them to log in once using one set of credentials to access multiple internal applications or services. 

![I have a question](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue85y11rcubtp19tw01a.gif)

### Authentication vs Authorization
I wanted to quickly distinguish the difference between **Single Sign-On (SSO) authentication** and **authorization** because they serve distinct yet interconnected purposes in the realm of user access control. SSO authentication primarily focuses on verifying the identity of a user, ensuring that they are who they claim to be, and granting them access to an internal system, application, or services.

**Authorization** is the process of defining and granting specific permissions and access rights to authenticated users, specifying what actions or resources they are allowed or denied within a system or application.

Now, you might be wondering how to bring this seamless SSO experience to your Next.js app for your startup/SMB/Enterprise users. That is what we are about to build and we have some resources to help us along the way. 

To better understand the implementation there are two GitHub repositories you should check out. 

1. [BoxyHQ's SAML SSO](https://github.com/boxyhq/jackson) - Primary single sign-on resource
 
2. [Source code](https://github.com/boxyhq/saas-starter-kit) -  for the Next.js SAML SSO integration

### Integrating SAML SSO into Your App

The integration of SAML Single Sign-On (SSO) into your app involves the following key steps:

- **Configure SAML Single Sign-On:**
This step enables your tenants to configure SAML connections for their users. Be sure to review the following guides for a deeper understanding of this process:

  - [UI Best Practices for Configuring SAML Single Sign-On](https://boxyhq.com/guides/jackson/configuring-saml-sso)
  - [SSO Connection API](https://boxyhq.com/docs/jackson/sso-flow/)

**Authenticate with SAML Single Sign-On:**
After adding a SAML connection, your app can utilize this SAML connection to initiate the SSO authentication flow using SAML Jackson. The following sections will focus more on the SSO authentication side.

…Yes that’s correct you heard it right - **SAML Jackson** 😉


[![SAML Jackson with a drink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grcy7xwbmxqsxmnmxn71.gif)](https://github.com/boxyhq/jackson)


[By the way when you head to the ⬆️ **SAML Jackson repo** ⬆️, would you please give me a star? ⭐](https://github.com/boxyhq/jackson)

Let’s get back to our regularly scheduled program and get coding. 🧑‍💻


### Install SAML Jackson
To get started with SAML Jackson, add it to your project's dependencies using Node Package Manager (NPM):


```
npm i --save @boxyhq/saml-jackson
```


### Setup SAML Jackson
Next, you'll need to configure SAML Jackson to work seamlessly with your Next.js app. This involves modifying your environment variables (.env) and creating a Jackson configuration file.

``` title=".env"
NEXTAUTH_URL=https://your-app.com
NEXTAUTH_SECRET= #A random string is used to hash tokens, sign/encrypt cookies, and generate cryptographic keys.
```

Before we can go any further we need to create a random string for our **NEXTAUTH_SECRET** shown above in our `.env` file. This can easily be done by downloading [OpenSSL](https://www.npmjs.com/package/openssl) and then typing this command in the terminal `openssl rand -base64 24` which will generate a random 32-character key.

Next, let’s create a new file `lib/jackson.ts`

```javascript

import jackson, {
  type IOAuthController,
  type JacksonOption,
} from "@boxyhq/saml-jackson";

const samlAudience = "https://saml.boxyhq.com";
const samlPath = "/api/oauth/saml";

const opts: JacksonOption = {
  externalUrl: `${process.env.NEXTAUTH_URL}`,
  samlAudience,
  samlPath,
  db: {
    engine: "sql",
    type: "postgres",
    url: "postgres://postgres:postgres@localhost:5432/postgres",
  },
};

let oauthController: IOAuthController;

const g = global as any;

export default async function init() {
  if (!g.oauthController) {
    const ret = await jackson(opts);

    oauthController = ret.oauthController;
    g.oauthController = oauthController;
  } else {
    oauthController = g.oauthController;
  }

  return {
    oauthController,
  };
}
```

**One quick note:** The `samlPath` is where the identity provider POSTs the SAML response after authenticating the user. 

_In brief, let's break down what our code is doing._ 
We first ensure that only a single Jackson controller instance is created and used throughout the application. Whenever we need to access the Jackson OAuth controller, you can import the `jackson` instance into the file where it’s needed.


### NextAuth.js Integration
For authentication, we'll use [NextAuth.js](https://next-auth.js.org/), a comprehensive open-source authentication solution designed for Next.js applications. Let’s go ahead and install it. 
(Note): I will add the link to [NextAuth’s docs](https://next-auth.js.org/providers/boxyhq-saml) covering SAML Jackson as a reference.

```
npm i --save next-auth
```

NextAuth ships with BoxyHQ SAML `boxyhq-saml` as a built-in SAML authentication provider. We'll use this provider to authenticate the users.

We'll now create a new file `pages/api/auth/[...nextauth].ts`

```javascript 
import NextAuth, { type NextAuthOptions } from 'next-auth';
import BoxyHQSAMLProvider from 'next-auth/providers/boxyhq-saml';

export const authOptions: NextAuthOptions = {
  providers: [
    BoxyHQSAMLProvider({
      authorization: { params: { scope: '' } },
      issuer: `${process.env.NEXTAUTH_URL}`,
      clientId: 'dummy',
      clientSecret: 'dummy',
      httpOptions: {
        timeout: 30000,
      },
    }),
  ],
  session: {
    strategy: 'jwt',
  },
};

export default NextAuth(authOptions);
```

_Let’s take a look at what’s going on here._
This code is essentially providing a set of instructions to your app on how to handle user logins securely. The `authOptions` object defines the provider and any other relevant settings, such as the session strategy. Then exports the NextAuth instance, passing in these authentication options, allowing the Next.js application to leverage the specified authentication provider and strategy for user authentication and session management.

### Making the Authentication Request
Now, let's add a route that initiates the authentication flow for SAML SSO by redirecting users to their configured Identity Provider.

Let's call this file `pages/api/oauth/authorize.t`

```javascript
import type { NextApiRequest, NextApiResponse } from "next";
import type { OAuthReq } from "@boxyhq/saml-jackson";

import jackson from "../../../../lib/jackson";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { oauthController } = await jackson();

  const { redirect_url } = await oauthController.authorize(
    req.query as unknown as OAuthReq
  );

  return res.redirect(302, redirect_url as string);
}
```

### Receiving the SAML Response
After successful authentication, the Identity Provider (IdP) POSTs the SAML response to the Assertion Consumer Service (ACS) URL. We need to create a route to handle this response.

New file `pages/api/oauth/saml.ts`

```javascript 
import type { NextApiRequest, NextApiResponse } from "next";

import jackson from "../../../../lib/jackson";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { oauthController } = await jackson();

  const { RelayState, SAMLResponse } = req.body;

  const { redirect_url } = await oauthController.samlResponse({
    RelayState,
    SAMLResponse,
  });

  return res.redirect(302, redirect_url as string);
}
```

### Requesting the Access Token
Next, we need a route to receive the callback after authentication. NextAuth requests an `access token` by passing the authorization code, along with authentication details including `grant_type`, `redirect_uri`, and `code_verifier`.

We want to now create a file `pages/api/oauth/token.ts`

```javascript 
import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { oauthController } = await jackson();

  const response = await oauthController.token(req.body);

  return res.json(response);
}
```

### Fetching the User Profile
Once the `access_token` has been fetched, NextAuth can use it to retrieve the user profile from the Identity Provider. The `userInfo` method returns a response containing the user profile if the authorization is valid.

We'll need a new file `pages/api/oauth/userinfo.ts`

```javascript 
import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { oauthController } = await jackson();

  const authHeader = req.headers['authorization'];

  if (!authHeader) {
    throw new Error('Unauthorized');
  }

  const token = authHeader.split(' ')[1];

  const user = await oauthController.userInfo(token);

  return res.json(user);
}
```

The response will contain user information, including their ID, email, first name, last name, and more. 

```json
{
  "id":"<id from the Identity Provider>",
  "email": "jackson@coolstartup.com",
  "firstName": "SAML",
  "lastName": "Jackson",
  "requested": {
    "tenant": "<tenant>",
    "product": "<product>",
    "client_id": "<client_id>",
    "state": "<state>"
  },
  "raw": {
    ...
  }
}
```

### Authenticating the User
Finally, once you've retrieved the user's information from the Identity Provider, you can determine if the user exists in your application and authenticate them accordingly. If the user doesn't exist, you can create a new record in your database and add them.

### Starts OAuth Sign In Flow
To initiate our application's OAuth sign-in flow, we will use NextAuth's `signIn` method and authenticate with the `boxyhq-saml` provider. 

You can pass the `tenant` and `product` as additional parameters to the `/api/oauth/authorize` endpoint through the third argument of `signIn()`.

:::info
Make sure you add a valid SAML connection for the tenant and product combination. Otherwise, the authentication will fail. Read about creating SAML connections [here](https://boxyhq.com/docs/jackson/deploy/npm-library#create-saml-connection)
:::

For this example app to work, you need to add a SAML connection for the tenant `boxyhq.com` and product `saml-demo.boxyhq.com` before you can authenticate the users.

Let's do that by creating `pages/login.tsx`

```javascript 
import type { NextPage } from 'next';
import { useSession, signIn } from 'next-auth/react';

const Login: NextPage = () => {
  const { data: session, status } = useSession();

  if (status === 'loading') {
    return <>Loading...</>;
  }

  if (status === 'authenticated') {
    return <>Authenticated</>;
  }

  // Starts OAuth sign-in flow
  signIn('boxyhq-saml', undefined, {
    tenant: 'boxyhq.com',
    product: 'saml-demo.boxyhq.com',
  });

  return <>Unauthenticated</>;
};

export default Login;
```

### Congratulations!


![High fives](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dcjfuz7fgyimhpud9zrw.gif)


### Let’s Take a Moment and Review What We've Learned 🥇

🎬 We started by exploring the world of Single Sign-On (SSO) and its transformative power to authenticate for your Next.js app. 

🔐 SSO simplifies user authentication, allowing the user to access multiple internal applications with a single login which is crucial for both security and user experience.

💡 Our focus has been on implementing SAML-based SSO, a robust and widely-used protocol. 

🗺️ We then walked through the process step-by-step, from configuring SAML Single Sign-On to authenticating users in your app.

📚 We learned how to set up SAML Jackson SSO, integrate it with NextAuth.js, and make the SSO magic happen with carefully crafted code snippets. Each section has brought you closer to creating a seamless SSO experience for your customers.

🚀 Let’s say goodbye to password fatigue and embrace the future of authentication in your Next.js app. With SAML SSO, you'll simplify login, enhance security, and elevate user satisfaction. We can now say we’ve successfully unlocked the doors to effortless authentication and a brighter future for your app!

### Community
If you have any questions along the way or get stuck while building your app join our [BoxyHQ Discord Developer Community](https://discord.boxyhq.com).