INTERCONNECT
← All articles
Entra IDAuthenticationNext.js

Entra ID Integration Patterns for Modern Web Applications

A technical guide to implementing Microsoft Entra ID authentication in Next.js applications with proper token handling, silent refresh, and conditional access alignment.

Interconnect
Engineering
January 24, 2025 · 4 min read

Microsoft Entra ID (formerly Azure AD) is the identity backbone for most enterprise and government organizations. Integrating it correctly into modern web applications requires understanding the OAuth 2.0 authorization code flow with PKCE, token lifecycle management, and conditional access policies.

Note: This guide covers the patterns we use at Interconnect for production Entra ID integrations in Next.js applications.

This guide covers the patterns we use at Interconnect for production Entra ID integrations.

The Authorization Code Flow

For web applications, the authorization code flow with PKCE is the correct pattern. Implicit flow is deprecated. Client credentials flow is for service-to-service communication, not user-facing applications.

The flow works like this:

  1. User clicks "Sign In"
  2. Application redirects to Entra ID with a code challenge
  3. User authenticates (and completes MFA if required)
  4. Entra ID redirects back with an authorization code
  5. Application exchanges the code for tokens
  6. Application stores tokens securely and establishes a session

MSAL Configuration

Microsoft's MSAL library handles the complexity of the OAuth flow. Here's a production-ready configuration:

typescript
1import { PublicClientApplication } from "@azure/msal-browser"
2
3const msalConfig = {
4 auth: {
5 clientId: process.env.NEXT_PUBLIC_AZURE_CLIENT_ID,
6 authority: `https://login.microsoftonline.com/${process.env.NEXT_PUBLIC_AZURE_TENANT_ID}`,
7 redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI,
8 postLogoutRedirectUri: process.env.NEXT_PUBLIC_POST_LOGOUT_URI,
9 },
10 cache: {
11 cacheLocation: "sessionStorage", // More secure than localStorage
12 storeAuthStateInCookie: true, // Required for IE11/Edge
13 },
14}
15
16export const msalInstance = new PublicClientApplication(msalConfig)

Token Acquisition Strategy

The key to smooth user experience is silent token acquisition. You should never prompt users to re-authenticate unless absolutely necessary.

typescript
1export async function getAccessToken(scopes: string[]): Promise<string> {
2 const accounts = msalInstance.getAllAccounts()
3
4 if (accounts.length === 0) {
5 throw new AuthenticationRequiredError()
6 }
7
8 const request = {
9 scopes,
10 account: accounts[0],
11 }
12
13 try {
14 // Try silent acquisition first
15 const response = await msalInstance.acquireTokenSilent(request)
16 return response.accessToken
17 } catch (error) {
18 if (error instanceof InteractionRequiredAuthError) {
19 // Fall back to interactive if silent fails
20 const response = await msalInstance.acquireTokenPopup(request)
21 return response.accessToken
22 }
23 throw error
24 }
25}

Handling Conditional Access

Conditional access policies can require step-up authentication mid-session. Your application must handle these challenges gracefully.

typescript
1export async function handleConditionalAccess(error: AuthError) {
2 if (error.errorCode === "interaction_required") {
3 const claims = error.claims ? JSON.parse(error.claims) : undefined
4
5 await msalInstance.acquireTokenPopup({
6 scopes: ["User.Read"],
7 claims: claims,
8 })
9 }
10}

API Route Protection

Every API route should validate the incoming token. Never trust client-side authentication alone.

typescript
1// app/api/protected/route.ts
2import { NextRequest, NextResponse } from "next/server"
3import { validateToken } from "@/lib/auth"
4
5export async function GET(request: NextRequest) {
6 const authHeader = request.headers.get("authorization")
7
8 if (!authHeader?.startsWith("Bearer ")) {
9 return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
10 }
11
12 const token = authHeader.substring(7)
13 const claims = await validateToken(token)
14
15 if (!claims) {
16 return NextResponse.json({ error: "Invalid token" }, { status: 401 })
17 }
18
19 // Proceed with authorized request
20 return NextResponse.json({ data: "Protected data" })
21}

Graph API Integration

Once authenticated, you can access Microsoft Graph for user data, organizational information, and more:

typescript
1export async function getUserProfile() {
2 const token = await getAccessToken(["User.Read"])
3
4 const response = await fetch("https://graph.microsoft.com/v1.0/me", {
5 headers: {
6 Authorization: `Bearer ${token}`,
7 },
8 })
9
10 if (!response.ok) {
11 throw new Error("Failed to fetch user profile")
12 }
13
14 return response.json()
15}

Session Management

For Next.js applications, we recommend using encrypted HTTP-only cookies to store session information:

typescript
1// lib/session.ts
2import { cookies } from "next/headers"
3import { encrypt, decrypt } from "@/lib/crypto"
4
5export async function createSession(userId: string, claims: TokenClaims) {
6 const session = {
7 userId,
8 claims,
9 expiresAt: Date.now() + 3600000, // 1 hour
10 }
11
12 const encrypted = await encrypt(JSON.stringify(session))
13 const cookieStore = await cookies()
14
15 cookieStore.set("session", encrypted, {
16 httpOnly: true,
17 secure: process.env.NODE_ENV === "production",
18 sameSite: "lax",
19 maxAge: 3600,
20 })
21}

Common Pitfalls

Warning: These are the most common security and UX issues we see in Entra ID implementations. Avoid them.

  1. Storing tokens in localStorage — Use sessionStorage or HTTP-only cookies instead
  2. Not validating tokens server-side — Client-side validation can be bypassed
  3. Ignoring token expiration — Implement proactive token refresh
  4. Hardcoding tenant IDs — Use configuration for multi-tenant support
  5. Not handling conditional access — Users will get stuck in authentication loops

Conclusion

Entra ID integration requires attention to security at every layer. The patterns above represent production-tested approaches that handle the complexity of enterprise authentication while maintaining good user experience.

Tip: For government environments (Azure Government), adjust the authority URLs to use login.microsoftonline.us and ensure your application is registered in the correct cloud.

For government environments (Azure Government), adjust the authority URLs accordingly and ensure your application is registered in the correct cloud.

Need Help With Your Modernization?

We help government agencies, regulated enterprises, and mission-driven organizations transform legacy systems into secure, identity-integrated platforms.

Get in Touch