Skip to main content

ADR-002: Authentication with Auth.js and Entra ID

Status

Accepted

Context

The application requires:

  1. Enterprise SSO: Integration with Microsoft Entra ID (Azure AD)
  2. Secure token handling: Access tokens must not be exposed to browsers
  3. Session management: Persistent sessions with automatic refresh
  4. Anonymous support: Some features available without authentication

Options considered:

  • Custom OAuth implementation: Full control but high maintenance
  • Auth0/Okta: Third-party dependency and cost
  • Auth.js (NextAuth): Open source, Next.js native, well-maintained

Decision

We will use Auth.js (NextAuth v5) with Microsoft Entra ID provider:

  1. OAuth flow: Authorization Code with PKCE
  2. Session strategy: JWT stored in HTTP-only cookies
  3. Token storage: Encrypted in session, server-side only
  4. Anonymous fallback: Client Credentials Flow for unauthenticated requests
// src/auth.ts
export const { auth, handlers } = NextAuth({
providers: [EntraID({
clientId: process.env.ENTRA_CLIENT_ID,
clientSecret: process.env.ENTRA_CLIENT_SECRET,
tenantId: process.env.ENTRA_TENANT_ID,
})],
session: { strategy: 'jwt' },
callbacks: {
jwt: async ({ token, account }) => {
if (account?.access_token) {
token.accessToken = account.access_token;
}
return token;
},
},
});

Anonymous User Support

For pre-authentication features, we use Client Credentials Flow:

  1. App authenticates with Entra using its own credentials
  2. Receives an access token without user context
  3. Backend identifies these as "anonymous" requests
  4. Limited to read-only, public data operations

Consequences

Positive

  • Security: Tokens never exposed to client JavaScript
  • Enterprise ready: Native Entra ID support
  • Automatic refresh: Auth.js handles token lifecycle
  • Middleware integration: Easy route protection

Negative

  • Auth.js versioning: Breaking changes between major versions
  • Debugging OAuth: Complex flow to troubleshoot
  • Entra configuration: Requires Azure portal setup

Neutral

  • Session size: JWT includes encrypted tokens
  • Callback URLs: Must be registered in Entra