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:
- Application (Client) ID
- Directory (Tenant) ID
- 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.
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"
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 };
Let's break down the steps we're taking here:
-
Firstly, we import the necessary modules:
-
NextAuth
andNextAuthOptions
from the"next-auth"
library. -
AzureADProvider
from the"next-auth/providers/azure-ad"
library. -
Cookies
method from the"next/headers"
library.
-
Next, we declare interfaces for
Session
andJWT
to extend their properties.-
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.
- We integrate the
Next, we initialize NextAuth with the configured
authOptions
object.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:
-
First we import necessary functions and types from the
next-auth/jwt
andnext/server
modules.-
getToken
is imported fromnext-auth/jwt
to retrieve the user's authentication token. -
NextResponse
andNextRequest
are imported fromnext/server
to handle HTTP responses and requests.
-
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.-
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.
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 fromnext-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.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 (1)
IT work well for normal , I need a help when i use localhost:3000 it works . i register reply url in azure like localhost:3000/webapp/ and webapp is added as basepath dynamically
than it is failing . can you provide the code for that