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.
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:
- User clicks "Sign In"
- Application redirects to Entra ID with a code challenge
- User authenticates (and completes MFA if required)
- Entra ID redirects back with an authorization code
- Application exchanges the code for tokens
- 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:
1import { PublicClientApplication } from "@azure/msal-browser"23const 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 localStorage12 storeAuthStateInCookie: true, // Required for IE11/Edge13 },14}1516export 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.
1export async function getAccessToken(scopes: string[]): Promise<string> {2 const accounts = msalInstance.getAllAccounts()3 4 if (accounts.length === 0) {5 throw new AuthenticationRequiredError()6 }78 const request = {9 scopes,10 account: accounts[0],11 }1213 try {14 // Try silent acquisition first15 const response = await msalInstance.acquireTokenSilent(request)16 return response.accessToken17 } catch (error) {18 if (error instanceof InteractionRequiredAuthError) {19 // Fall back to interactive if silent fails20 const response = await msalInstance.acquireTokenPopup(request)21 return response.accessToken22 }23 throw error24 }25}Handling Conditional Access
Conditional access policies can require step-up authentication mid-session. Your application must handle these challenges gracefully.
1export async function handleConditionalAccess(error: AuthError) {2 if (error.errorCode === "interaction_required") {3 const claims = error.claims ? JSON.parse(error.claims) : undefined4 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.
1// app/api/protected/route.ts2import { NextRequest, NextResponse } from "next/server"3import { validateToken } from "@/lib/auth"45export 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 }1112 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 }1819 // Proceed with authorized request20 return NextResponse.json({ data: "Protected data" })21}Graph API Integration
Once authenticated, you can access Microsoft Graph for user data, organizational information, and more:
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 })910 if (!response.ok) {11 throw new Error("Failed to fetch user profile")12 }1314 return response.json()15}Session Management
For Next.js applications, we recommend using encrypted HTTP-only cookies to store session information:
1// lib/session.ts2import { cookies } from "next/headers"3import { encrypt, decrypt } from "@/lib/crypto"45export async function createSession(userId: string, claims: TokenClaims) {6 const session = {7 userId,8 claims,9 expiresAt: Date.now() + 3600000, // 1 hour10 }1112 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.
- Storing tokens in localStorage — Use sessionStorage or HTTP-only cookies instead
- Not validating tokens server-side — Client-side validation can be bypassed
- Ignoring token expiration — Implement proactive token refresh
- Hardcoding tenant IDs — Use configuration for multi-tenant support
- 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.usand 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