Integrating Azure AD as an Identity provider in Next.js with NextAuth
Tags: nextjs, nextauth, azuread, authentication
Published: 2024-03-10 10:32:18


In this guide, I'll walk you through the process of smoothly setting up AzureAD as the identity provider for your Next.js application using the app router.

## Prerequisites

This guide assumes that you have already registered your application in Microsoft Azure and have access to the following credentials:

1. Application (Client) ID
2. Directory (Tenant) ID
3. Client Secret

For the purpose of this tutorial, let's assume the registered application is named DEMO-APP.

Now, let's quickly go through a checklist to ensure that we have all the necessary configurations in place for integrating Azure AD with our Next.js app.

Go to **_App Registration_** -> **_DEMO-APP_** and choose the Authentication tab from the left-hand pane.

### 1. Configure Redirect URI 

Configure the redirect URLs for our application under the Web (Redirect URIs) section. These URLs will serve as destinations for authentication responses (tokens) after successfully authenticating or signing out users.

The format of the redirect URL is as follows: `APP_BASE_URL/api/auth/callback/azure-ad`

For example:

- Local development redirect URL - http://localhost:4000/api/auth/callback/azure-ad
- Production environment redirect URL - https://your-custom-domain.com/api/auth/callback/azure-ad

### 2. Enable Implicit grant and hybrid flows

Choose both access tokens and ID tokens under the Implicit grant and hybrid flows section.

![enable-implicit-grant-hybrid-flows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfcsnvoyp5yqbdumthga.png)


### 3. Set up the permissions for accessing your application

To restrict access to your application or APIs within your organization, choose "**Accounts in this organizational directory only – (Single Tenant)**".

Then, click on **Save** to apply the configured authentication settings.

## Integrating Azure AD with Next.js Application 

Proceed with the following steps to integrate your Next.js application with Azure AD:

### 1. Update the necessary credentials in the environment (env) file.

> 
**AZURE_AD_CLIENT_ID** - _Application (Client) ID_
**AZURE_AD_CLIENT_SECRET** - _Client Secret_
**AZURE_AD_TENANT_ID** - _Directory (Tenant) ID_
**NEXTAUTH_SECRET** - _Generate a random secret for NextAuth_
**NEXTAUTH_URL** - _Next.js application base URL_
**NEXTAUTH_URL_INTERNAL** - _Next.js application base URL_


```
AZURE_AD_CLIENT_ID="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
AZURE_AD_CLIENT_SECRET="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
AZURE_AD_TENANT_ID="XXXX-XXXX-XXXX-XXXX-XXXX"
NEXTAUTH_SECRET="XXXXXXXXXXXXXXXXX"
NEXTAUTH_URL="http://localhost:4000"
NEXTAUTH_URL_INTERNAL="http://127.0.0.1:4000"
```

> 
**<u>Note:</u>** It is crucial to implement appropriate security measures when adding secrets to the env.production file. 

### 2. Configuring Authentication for a Next.js Application Using Azure AD as the Authentication Provider and NextAuth

Create a file named **_route.ts_** within the **_app/api/auth/[...nextauth]_** folder, and then paste the following code into it.

```
import NextAuth, { NextAuthOptions } from "next-auth";
import AzureADProvider from "next-auth/providers/azure-ad";
import { cookies } from 'next/headers'

declare module "next-auth" {
  interface Session {
    accessToken?: string;
    idToken?: string;
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    provider?: string;
    accessToken?: string;
    idToken?: string;
  }
}

const authOptions: NextAuthOptions = {
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID as string,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET as string,
      tenantId: process.env.AZURE_AD_TENANT_ID as string,
      authorization: { params: { scope: "openid profile user.Read email" } },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET as string,
  callbacks: {
    async jwt({ token, account}) {
      if (account) {
        token.idToken = account.id_token as string;
        token.accessToken = account.access_token as string;
      }
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken as string;
      session.idToken = token.idToken as string;
      cookies().set('idToken', session.idToken);
      return session;
    },
  },
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
```

Let's break down the steps we're taking here:

1. Firstly, we import the necessary modules:
   - `NextAuth` and `NextAuthOptions` from the `"next-auth"` library.
   - `AzureADProvider` from the `"next-auth/providers/azure-ad"` library.
   - `Cookies` method from the `"next/headers"` library.

2. Next, we declare interfaces for `Session` and `JWT` to extend their properties.

3. Then, we proceed to configure the authentication options:
   - We integrate the `Azure AD provider` with essential parameters like clientId, clientSecret, tenantId, and authorization scope.
   - Callbacks are set for JWT and session handling:
         - The _jwt_ callback extracts the idToken and accessToken from the Azure AD account object and incorporates them into the JWT token.
         - The _session_ callback adds accessToken and idToken to the session object and sets a cookie named 'idToken' with the idToken value.

4. Next, we initialize NextAuth with the configured `authOptions` object.

5. Finally, we export the NextAuth handler for GET and POST requests.


### 3. Initiate Sign In

Include the following lines of code in your client component, where you want to link the signIn action:

```
import { signIn } from "next-auth/react";
```

```
<button onClick={() => { signIn('azure-ad') }}>
Sign In
</button>
```

Calling `signIn()` without any arguments will redirect to the prebuilt sign-in page provided by NextAuth, featuring a button for signing in via Azure AD. Alternatively, directly **_invoke the signIn function with the provider's name_** (in this case, "azure-ad" - `signIn('azure-ad')`) from the JSX code to bypass the default sign-in page.

### 4. Initiate Sign Out

Include the following lines of code in your client component, where you want to link the signOut action:

```
import { signOut } from 'next-auth/react';
```

```
async function handleLogout() {
  await signOut({ redirect: false, callbackUrl: "/" });
  push('/');
}
```

```
<button onClick={() => {handleLogout}}>
Sign Out
</button>
```

## Middleware


Having integrated Azure AD as the identity provider with our application, the subsequent task involves safeguarding our endpoints from unauthorized access. Here's what you need to do:

Generate a file named **_middleware.ts_** within the **_root directory_** of your application.

Embed the provided code snippet into the middleware file.

```
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

// API Paths to be restricted.
const protectedRoutes = ["/hub"]; 

export default async function middleware(request: NextRequest) {
  const res = NextResponse.next();
  const pathname = request.nextUrl.pathname;
  if (protectedRoutes.some((path) => pathname.startsWith(path))) {
    const token = await getToken({
      req: request
    });
    // check not logged in.
    if (!token) {
      const url = new URL("/", request.url);
      return NextResponse.redirect(url);
    }
 }
 return res;
}
```

Here's a breakdown of the steps:

1. First we import necessary functions and types from the `next-auth/jwt` and `next/server` modules. 
  - `getToken` is imported from `next-auth/jwt` to retrieve the user's authentication token. 
  - `NextResponse` and `NextRequest` are imported from `next/server` to handle HTTP responses and requests.

2. An array named protectedRoutes is declared, containing the paths of the routes that require protection. In this example, any route that starts with `/hub` route is protected.

3. The middleware function is exported as the default export. It takes a NextRequest object as a parameter.
  - Inside the middleware function, a NextResponse object res is initialized using NextResponse.next().
  - The pathname of the incoming request is extracted from the nextUrl property of the request object.

4. If the requested path matches any of the paths in the protectedRoutes array, the middleware attempts to retrieve the user's authentication token using the `getToken` function from `next-auth/jwt`. This token represents the user's authentication state.
If no token is found (indicating that the user is not logged in), the middleware redirects the user to the root path of the application.

5. Finally, the middleware returns the NextResponse object res. If the requested path does not match any protected routes, the middleware allows the request to proceed without any additional action.

And there you have it! 

In this article, we've successfully integrated our Next.js application with Azure AD as the authentication provider. As a bonus, we've also gained insight into securing our application APIs against unauthorized access by implementing a middleware.