This guide will help you configure NextAuth to authenticate users via Azure Active Directory and protect all pages of your application, if you're using the App Router feature in Next.js 13+ (including Next.js 14).
This project is using pnpm. We assume that the project is using an src
directory.
-
Register an App in Microsoft Entra ID/Azure AD:
- Go to the Azure Portal and navigate to Microsoft Entra ID/ > App registrations > New registration.
- Name your app, set the redirect URI to
http://localhost:3000/api/auth/callback/azure-ad
, and register it. - After registration, go to Certificates & secrets and create a new client secret. Note down the secret value.
- Under Authentication, ensure the redirect URI is added and set to "Web" type.
- Note the Application (client) ID and Directory (tenant) ID from the Overview page.
-
API Permissions:
- Go to API permissions and add the following permissions:
- Microsoft Graph > Delegated permissions >
email
,openid
,profile
.
- Microsoft Graph > Delegated permissions >
- Grant admin consent for the permissions.
- Go to API permissions and add the following permissions:
In your Next.js app, install the necessary dependencies:
pnpm install next-auth
-
Create a new AuthOptions for NextAuth in the App Router structure. Place this file under
src/libs/auth.ts
.// src/libs/auth.ts import { NextAuthOptions } from 'next-auth' import AzureADProvider from 'next-auth/providers/azure-ad' export const authOptions: NextAuthOptions = { providers: [ AzureADProvider({ clientId: process.env.AZURE_AD_CLIENT_ID || '', clientSecret: process.env.AZURE_AD_CLIENT_SECRET || '', tenantId: process.env.AZURE_AD_TENANT_ID || '', }), ], callbacks: { async session({ session, token }) { // Safely assign the user ID from the token if (session.user && token.sub) { session.user.id = token.sub // Assigning user ID safely } return session }, }, secret: process.env.NEXTAUTH_SECRET, theme: { colorScheme: 'light', logo: '/next.svg', }, }
You can customize the default login page by defining a
theme
and set the defaultcolorScheme
and alogo
.To avoid TypeScript errors, you need to extend the default
Session
andUser
types in NextAuth. This can be done by creating a new file to add custom types. -
Create a
next-auth.d.ts
file in your project to extend the default types:// src/types/next-auth.d.ts import { DefaultSession, DefaultUser } from 'next-auth' // Extend the default user type declare module 'next-auth' { interface Session { user: { id: string // Add `id` property to the session user } & DefaultSession['user'] } interface User extends DefaultUser { id: string // Add `id` property to the user } }
-
Ensure TypeScript uses this type file by adding it to your
tsconfig.json
under theinclude
ortypeRoots
:// tsconfig.json { "compilerOptions": { "target": "esnext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/types/next-auth.d.ts"], "exclude": ["node_modules"] }
-
Create a new API route for NextAuth in the App Router structure. Place this file under
src/app/api/auth/[...nextauth]/route.ts
.// src/app/api/auth/[...nextauth]/route.ts import { authOptions } from '@/libs/auth' import NextAuth from 'next-auth' const handler = NextAuth(authOptions) export { handler as GET, handler as POST }
Create a .env.local
file in the root of your project with the following environment variables:
AZURE_AD_CLIENT_ID=your_client_id
AZURE_AD_CLIENT_SECRET=your_client_secret
AZURE_AD_TENANT_ID=your_tenant_id
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
Replace your_client_id
, your_client_secret
, and your_tenant_id
with the values obtained from Microsoft Entra ID/Azure AD.
To protect pages, you can use a middleware.ts
file to enforce authentication across your entire app or selectively for certain routes.
Create a middleware.ts
file in the root of your project:
// src/middleware.ts
export { default } from 'next-auth/middleware'
export const config = {
matcher: ['/((?!api|_next|static|.*\\..*|favicon.ico).*)'],
}
This middleware will run on every route except for specific paths like /api
, Next.js internal paths (/_next
), and static files. It will check if the user is authenticated and redirect to the sign-in page if they are not. It will also ignore the facicon.ico
.
If you want to protect only specific routes in your app, you can adapt the regex
accordingly.
To access the session in server components, use the getServerSession
function from NextAuth. This approach works well since server components can directly work with the session data on the server.
Here's how you can protect your server components and check for session data:
-
Create a Helper to Fetch Session:
Create a helper to fetch the session on the server:
// src/libs/auth.ts import { NextAuthOptions, getServerSession } from 'next-auth' import AzureADProvider from 'next-auth/providers/azure-ad' export const authOptions: NextAuthOptions = { ... } export async function getSession() { return await getServerSession(authOptions) }
-
Use the Helper in a Server Component:
Now, you can use this helper in any server component to protect it:
// src/app/page.tsx (or any other server component) import { getSession } from '@/lib/auth' export default async function HomePage() { const session = await getSession() // Check if the user is authenticated if (!session) { return <p>You must be logged in to access this page.</p> } return <div>Welcome, {session.user?.name}!</div> }
-
Handling Authentication States:
- Unauthenticated Users: You can choose to redirect them to the sign-in page, show a message, or handle the state as per your app’s requirements.
- Authenticated Users: Display personalized content based on the session data.
-
Create a Logout Button client component:
Since server components don’t handle events like button clicks directly, you need a client component to trigger the logout.
// src/app/components/LogoutButton.tsx 'use client' import { signOut } from 'next-auth/react' export default function LogoutButton() { const handleLogout = () => { signOut({ callbackUrl: '/', // Redirect to this URL after logging out (e.g., home page) }) } return ( <button onClick={handleLogout} className='bg-red-500 text-white px-4 py-2 rounded'> Logout </button> ) }
-
Create a User Profile: client component:
Use the
useSession
hook to access the session data in your components:// src/app/components/UserProfile.tsx 'use client' import { useSession } from 'next-auth/react' export default function UserProfile() { const { data: session, status } = useSession() if (status === 'loading') { return <p>Loading...</p> } if (!session) { return <p>You are not logged in.</p> } return <div>Welcome, {session.user?.name}!</div> }
-
Create a Wrapper using a Session Provider
SessionProvider
needs to be used in a client component that specifies'use client'
. To avoid problems withlayout.tsx
which is a server component, we create a simple wrapper.// src/app/components/Wrapper.tsx 'use client' import { SessionProvider } from 'next-auth/react' import { PropsWithChildren } from 'react' export default function Wrapper({ children }: PropsWithChildren) { return <SessionProvider>{children}</SessionProvider> }
-
Wrap the children with the Wrapper in
layout.tsx
To limit the scope of the client components, we only wrap the components which need a session.
// src/app/layout.tsx import LogoutButton from '@/components/LogoutButton' import UserProfile from '@/components/UserProfile' import Wrapper from '@/components/Wrapper' export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( <html lang='en'> <body className={inter.className}> <header className='flex items-center justify-between p-4'> <Wrapper> <UserProfile /> <LogoutButton /> </Wrapper> </header> <main>{children}</main> </body> </html> ) }
- Microsoft Entra ID/Azure AD Setup: Register your application, configure secrets, and set permissions in Azure.
- NextAuth Configuration: Use the Microsoft Entra ID/Azure AD provider in NextAuth.
- Middleware Protection: Create a middleware to protect routes.
- Access Session in Server Components: Use getServerSession to check authentication state directly in server components.
- Access Session in Client Components: Use the
useSession
hook in components to manage user state.
This setup ensures that all pages are protected with Microsoft Entra ID/Azure AD authentication in a Next.js 14 App Router environment using NextAuth.js.