DEV Community

Cover image for Integrating Azure AD as an Identity provider in Next.js with NextAuth
Sruthi Viswanathan
Sruthi Viswanathan

Posted on

Integrating Azure AD as an Identity provider in Next.js with NextAuth

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:

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

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"
Enter fullscreen mode Exit fullscreen mode

Note: 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 };
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode
<button onClick={() => { signIn('azure-ad') }}>
Sign In
</button>
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode
async function handleLogout() {
  await signOut({ redirect: false, callbackUrl: "/" });
  push('/');
}
Enter fullscreen mode Exit fullscreen mode
<button onClick={() => {handleLogout}}>
Sign Out
</button>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)