28 KiB
Authentication Flow Audit - NextAuth with Keycloak & SSO for Iframe Applications
Executive Summary
This document provides a comprehensive audit of the authentication architecture in the Neah dashboard application. The system uses NextAuth.js v4 with Keycloak as the OAuth provider, implementing JWT-based sessions and supporting Single Sign-On (SSO) for multiple iframe-embedded applications via cookie-based authentication.
Architecture Overview
Components
- NextAuth.js - Authentication framework
- Keycloak - Identity Provider (IdP) via OAuth 2.0/OpenID Connect
- JWT Strategy - Session management (no database sessions)
- Iframe Applications - Multiple embedded applications using SSO via cookies
- Keycloak Admin Client - Server-side user management
1. Authentication Entry Points
1.1 Sign-In Page (/app/signin/page.tsx)
Location: app/signin/page.tsx
Flow:
1. User navigates to /signin
2. Component automatically triggers: signIn("keycloak", { callbackUrl: "/" })
3. Redirects to Keycloak authorization endpoint
4. After Keycloak authentication, initializes storage via /api/storage/init
5. Redirects to home page
Key Methods:
signIn("keycloak")- NextAuth client-side method- Automatic redirect to Keycloak OAuth flow
- Storage initialization after successful authentication
Dependencies:
next-auth/react- Client-side NextAuth hooks- Storage API endpoint for user space initialization
2. NextAuth Configuration
2.1 Route Handler (/app/api/auth/[...nextauth]/route.ts)
Location: app/api/auth/[...nextauth]/route.ts
Purpose: NextAuth API route handler for all authentication endpoints
Endpoints Handled:
GET/POST /api/auth/signin- Sign inGET/POST /api/auth/signout- Sign outGET /api/auth/session- Get current sessionGET /api/auth/csrf- CSRF tokenGET /api/auth/providers- Available providersGET /api/auth/callback/keycloak- OAuth callback
Implementation:
import NextAuth from "next-auth";
import { authOptions } from "../options";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
2.2 Auth Options Configuration (/app/api/auth/options.ts)
Location: app/api/auth/options.ts
This is the core authentication configuration file.
2.2.1 Keycloak Provider Setup
KeycloakProvider({
clientId: getRequiredEnvVar("KEYCLOAK_CLIENT_ID"),
clientSecret: getRequiredEnvVar("KEYCLOAK_CLIENT_SECRET"),
issuer: getRequiredEnvVar("KEYCLOAK_ISSUER"),
authorization: {
params: {
scope: "openid profile email roles" // Requested OAuth scopes
}
},
profile(profile) { /* Profile transformation */ }
})
Environment Variables Required:
KEYCLOAK_CLIENT_ID- OAuth client identifierKEYCLOAK_CLIENT_SECRET- OAuth client secretKEYCLOAK_ISSUER- Keycloak realm issuer URL (e.g.,https://keycloak.example.com/realms/neah)
OAuth Scopes Requested:
openid- OpenID Connect coreprofile- User profile informationemail- User email addressroles- User roles from Keycloak realm
2.2.2 Profile Callback
Location: Lines 109-137 in options.ts
Purpose: Transforms Keycloak user profile into NextAuth user object
Process:
- Receives Keycloak profile with
realm_access.roles - Extracts roles from
realm_access.rolesarray - Cleans roles by:
- Removing
ROLE_prefix (if present) - Converting to lowercase
- Removing
- Maps Keycloak profile fields to NextAuth user:
sub→idnameorpreferred_username→nameemail→emailgiven_name→first_namefamily_name→last_namepreferred_username→username- Cleaned roles →
role[]
Code Flow:
profile(profile) {
const roles = profile.realm_access?.roles || [];
const cleanRoles = roles.map((role: string) =>
role.replace(/^ROLE_/, '').toLowerCase()
);
return {
id: profile.sub,
name: profile.name ?? profile.preferred_username,
email: profile.email,
first_name: profile.given_name ?? '',
last_name: profile.family_name ?? '',
username: profile.preferred_username ?? profile.email?.split('@')[0] ?? '',
role: cleanRoles,
}
}
2.2.3 Session Configuration
Location: Lines 140-143
session: {
strategy: "jwt", // JWT-based sessions (no database)
maxAge: 30 * 24 * 60 * 60, // 30 days
}
Characteristics:
- Strategy: JWT (stateless, no database lookups)
- Max Age: 30 days (2,592,000 seconds)
- Storage: Encrypted JWT stored in HTTP-only cookies
2.2.4 JWT Callback
Location: Lines 145-181
Purpose: Handles JWT token creation and refresh
Flow:
Initial Authentication (account & profile present):
if (account && profile) {
1. Extract roles from Keycloak profile
2. Clean roles (remove ROLE_ prefix, lowercase)
3. Store in JWT token:
- accessToken: account.access_token (Keycloak access token)
- refreshToken: account.refresh_token (Keycloak refresh token)
- accessTokenExpires: account.expires_at (expiration timestamp)
- sub: Keycloak user ID
- role: cleaned roles array
- username, first_name, last_name: from profile
}
Subsequent Requests (token refresh check):
else if (token.accessToken) {
1. Decode JWT to extract roles (if not already in token)
2. Check if token is expired:
- If expired: Call refreshAccessToken()
- If valid: Return existing token
}
Token Expiration Check:
if (Date.now() < (token.accessTokenExpires as number) * 1000) {
return token; // Token still valid
}
return refreshAccessToken(token); // Token expired, refresh
Note: There's a BUG in line 176 - it multiplies accessTokenExpires by 1000, but expires_at from Keycloak is already in seconds since epoch. This should be checked.
2.2.5 Token Refresh Function
Location: Lines 64-96
Purpose: Refreshes expired Keycloak access tokens
Implementation:
async function refreshAccessToken(token: JWT) {
1. POST to Keycloak token endpoint:
- URL: ${KEYCLOAK_ISSUER}/protocol/openid-connect/token
- Method: POST
- Body:
* client_id: KEYCLOAK_CLIENT_ID
* client_secret: KEYCLOAK_CLIENT_SECRET
* grant_type: refresh_token
* refresh_token: token.refreshToken
2. On Success:
- Update accessToken
- Update refreshToken (if new one provided)
- Update accessTokenExpires: Date.now() + expires_in * 1000
3. On Error:
- Set token.error = "RefreshAccessTokenError"
- Return token with error flag
}
Error Handling: Sets token.error flag which is checked in session callback
2.2.6 Session Callback
Location: Lines 182-202
Purpose: Transforms JWT token into session object for client-side use
Flow:
async session({ session, token }) {
1. Check for refresh errors:
if (token.error) throw new Error(token.error)
2. Build session.user object:
- id: token.sub (Keycloak user ID)
- email: token.email
- name: token.name
- image: null
- username: token.username
- first_name: token.first_name
- last_name: token.last_name
- role: token.role (array)
- nextcloudInitialized: false (default)
3. Add accessToken to session:
session.accessToken = token.accessToken
4. Return session
}
Important: The accessToken (Keycloak OAuth token) is exposed in the session object, making it available client-side via useSession() hook.
2.2.7 Custom Pages
Location: Lines 204-207
pages: {
signIn: '/signin',
error: '/signin',
}
Custom Routes:
- Sign-in page:
/signin(instead of default/api/auth/signin) - Error page:
/signin(redirects to sign-in on errors)
3. Authentication Flow Step-by-Step
3.1 Initial Sign-In Flow
┌─────────────┐
│ Browser │
└──────┬──────┘
│
│ 1. GET /signin
▼
┌─────────────────────┐
│ /app/signin/page.tsx │
│ - Auto-triggers │
│ signIn("keycloak") │
└──────┬──────────────┘
│
│ 2. Redirect to NextAuth
▼
┌──────────────────────────────┐
│ /api/auth/signin/keycloak │
│ - Generates OAuth state │
│ - Redirects to Keycloak │
└──────┬───────────────────────┘
│
│ 3. GET /realms/{realm}/protocol/openid-connect/auth
│ ?client_id=...
│ &redirect_uri=...
│ &response_type=code
│ &scope=openid profile email roles
│ &state=...
▼
┌─────────────────────┐
│ Keycloak Server │
│ - Login page │
│ - User credentials │
└──────┬──────────────┘
│
│ 4. User authenticates
│
│ 5. POST /realms/{realm}/protocol/openid-connect/token
│ (Authorization code exchange)
│
│ 6. Keycloak returns:
│ - access_token
│ - refresh_token
│ - id_token
│ - expires_in
▼
┌──────────────────────────────┐
│ /api/auth/callback/keycloak │
│ - Receives authorization code│
│ - Exchanges for tokens │
│ - Fetches user profile │
└──────┬───────────────────────┘
│
│ 7. JWT Callback
│ - Stores tokens in JWT
│ - Extracts user info
│ - Cleans roles
│
│ 8. Session Callback
│ - Builds session object
│
│ 9. Sets NextAuth cookies:
│ - next-auth.session-token (encrypted JWT)
│ - next-auth.csrf-token
▼
┌─────────────────────┐
│ Browser (Client) │
│ - Cookies set │
│ - Redirect to / │
└──────┬──────────────┘
│
│ 10. GET / (home page)
│ - getServerSession() validates JWT
│ - Session available
▼
┌─────────────────────┐
│ Dashboard Loaded │
└─────────────────────┘
3.2 Subsequent Request Flow (Authenticated)
┌─────────────┐
│ Browser │
└──────┬──────┘
│
│ 1. GET /any-page
│ Cookie: next-auth.session-token=...
▼
┌──────────────────────────────┐
│ Next.js Server │
│ getServerSession(authOptions)│
└──────┬───────────────────────┘
│
│ 2. Decrypt JWT from cookie
│
│ 3. Check token expiration
│
│ 4a. If valid:
│ - Extract user info
│ - Return session
│
│ 4b. If expired:
│ - Call refreshAccessToken()
│ - POST to Keycloak /token
│ - Update JWT with new tokens
│ - Return session
▼
┌─────────────────────┐
│ Page Component │
│ - session available│
└─────────────────────┘
3.3 Token Refresh Flow
┌─────────────────────┐
│ JWT Callback │
│ (Token expired) │
└──────┬──────────────┘
│
│ 1. Call refreshAccessToken()
│
│ 2. POST ${KEYCLOAK_ISSUER}/protocol/openid-connect/token
│ Body:
│ - client_id
│ - client_secret
│ - grant_type: refresh_token
│ - refresh_token: <current_refresh_token>
▼
┌─────────────────────┐
│ Keycloak Server │
│ - Validates refresh│
│ token │
│ - Issues new tokens│
└──────┬──────────────┘
│
│ 3. Returns:
│ - access_token (new)
│ - refresh_token (new, optional)
│ - expires_in
▼
┌─────────────────────┐
│ Update JWT Token │
│ - New accessToken │
│ - New refreshToken │
│ - New expires time │
└──────┬──────────────┘
│
│ 4. Return updated token
│
│ 5. Session callback builds session
▼
┌─────────────────────┐
│ Session Available │
└─────────────────────┘
4. Iframe SSO Architecture
4.1 Overview
The dashboard embeds multiple applications in iframes. These applications rely on cookie-based SSO to authenticate users automatically using the Keycloak session established in the parent dashboard.
4.2 Iframe Application Pages
Pattern: All iframe pages follow the same structure:
// Example: app/parole/page.tsx
export default async function Page() {
const session = await getServerSession(authOptions);
if (!session) {
redirect("/signin");
}
return (
<ResponsiveIframe
src={process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL || ''}
/>
);
}
Iframe Applications Identified:
- Parole (
/parole) -NEXT_PUBLIC_IFRAME_PAROLE_URL - Agilite (
/agilite) -NEXT_PUBLIC_IFRAME_AGILITY_URL - Alma (
/alma) -NEXT_PUBLIC_IFRAME_AI_ASSISTANT_URL - Vision (
/vision) -NEXT_PUBLIC_IFRAME_CONFERENCE_URL - The Message (
/the-message) -NEXT_PUBLIC_IFRAME_THEMESSAGE_URL - WP Admin (
/wp-admin) -NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL - Mediation (
/mediation) -NEXT_PUBLIC_IFRAME_MEDIATIONS_URL - Apprendre (
/apprendre) -NEXT_PUBLIC_IFRAME_LEARN_URL - Gite (
/gite) -NEXT_PUBLIC_IFRAME_GITE_URL - Artlab (
/artlab) -NEXT_PUBLIC_IFRAME_ARTLAB_URL - Calcul (
/calcul) -NEXT_PUBLIC_IFRAME_CALCULATION_URL - Chapitre (
/chapitre) -NEXT_PUBLIC_IFRAME_CHAPTER_URL - Dossiers (
/dossiers) -NEXT_PUBLIC_IFRAME_DRIVE_URL - CRM (
/crm) -NEXT_PUBLIC_IFRAME_MEDIATIONS_URL - Livres (
/livres) -NEXT_PUBLIC_IFRAME_LIVRE_URL - Showcase (
/showcase) -NEXT_PUBLIC_IFRAME_SHOWCASE_URL - Radio (
/radio) -NEXT_PUBLIC_IFRAME_RADIO_URL - Press (
/press) -NEXT_PUBLIC_IFRAME_SHOWCASE_URL - Observatory -
NEXT_PUBLIC_IFRAME_OBSERVATORY_URL - Time Tracker -
NEXT_PUBLIC_IFRAME_TIMETRACKER_URL - Missions Board -
NEXT_PUBLIC_IFRAME_MISSIONSBOARD_URL - Carnet -
NEXT_PUBLIC_IFRAME_CARNET_URL
4.3 SSO Cookie Mechanism
How It Works:
-
Parent Dashboard Authentication:
- User authenticates via Keycloak in the dashboard
- Keycloak sets authentication cookies (domain: Keycloak domain)
- NextAuth sets session cookies (domain: dashboard domain)
-
Iframe Cookie Sharing:
- When iframe loads, browser sends cookies for the iframe's domain
- If iframe application is on same domain or subdomain of Keycloak:
- Keycloak cookies are automatically sent
- Application can read Keycloak session cookies
- SSO works automatically
-
Cross-Domain Considerations:
- If iframe apps are on different domains, they need:
- Same Keycloak realm configuration
- Proper CORS settings
- Cookie domain configuration in Keycloak
SameSite=None; Securecookie attributes for cross-site
- If iframe apps are on different domains, they need:
4.4 ResponsiveIframe Component
Location: app/components/responsive-iframe.tsx
Features:
- Auto-resizing based on viewport
- Hash synchronization (URL fragments)
- Full-screen support
Important: This component does NOT handle authentication - it's purely presentational. SSO relies on browser cookie behavior.
5. Sign-Out Flow
5.1 Sign-Out Page
Location: app/signout/page.tsx
Implementation:
export default function SignOut() {
return (
<div>
<SignOutHandler />
<p>Déconnexion en cours...</p>
</div>
);
}
5.2 Sign-Out Handler
Location: components/auth/signout-handler.tsx
Flow:
1. clearAuthCookies() - Clears NextAuth cookies client-side
2. signOut({ callbackUrl: "/signin", redirect: true })
- Calls NextAuth signout endpoint
- Invalidates session
- Redirects to /signin
5.3 Cookie Clearing
Location: lib/session.ts - clearAuthCookies()
Implementation:
export function clearAuthCookies() {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const [name] = cookie.split('=');
if (name.trim().startsWith('next-auth.') ||
name.trim().startsWith('__Secure-next-auth.') ||
name.trim().startsWith('__Host-next-auth.')) {
document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
}
}
Note: This only clears NextAuth cookies. Keycloak cookies remain unless:
- User manually logs out of Keycloak
- Keycloak session expires
- Application calls Keycloak logout endpoint
5.4 Service Token Invalidation
Location: lib/session.ts - invalidateServiceTokens()
Purpose: Logs out from integrated services (RocketChat, Leantime, etc.)
Services Handled:
- RocketChat:
/api/v1/logout - Leantime: JSON-RPC logout method
Note: This function exists but may not be called during standard sign-out flow.
6. Server-Side Session Access
6.1 getServerSession()
Usage Pattern (seen in all iframe pages):
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/options";
const session = await getServerSession(authOptions);
How It Works:
- Reads
next-auth.session-tokencookie from request - Decrypts JWT using
NEXTAUTH_SECRET - Validates token signature and expiration
- If expired, triggers refresh (via JWT callback)
- Returns session object
Location: Used in:
- All iframe page components
- Root layout (
app/layout.tsx) - Any server component needing authentication
6.2 Client-Side Session Access
Usage Pattern:
import { useSession } from "next-auth/react";
const { data: session, status } = useSession();
How It Works:
useSession()hook calls/api/auth/session- Server decrypts JWT and returns session
- Client receives session object
- Automatically refetches when token refreshes
Location: Used in:
app/signin/page.tsxcomponents/auth/auth-check.tsx- Any client component needing authentication
7. Keycloak Admin Client
7.1 Purpose
Location: lib/keycloak.ts
The Keycloak Admin Client is used for server-side user management, not for user authentication. It's a separate administrative interface.
7.2 Authentication Methods
Two Methods Supported:
-
Client Credentials (Preferred):
grant_type: 'client_credentials' client_id: KEYCLOAK_CLIENT_ID client_secret: KEYCLOAK_CLIENT_SECRET -
Password Grant (Fallback):
grant_type: 'password' client_id: KEYCLOAK_CLIENT_ID username: KEYCLOAK_ADMIN_USERNAME password: KEYCLOAK_ADMIN_PASSWORD
7.3 Caching
Token Caching: 5 minutes
- Validates cached token before reuse
- Creates new client if token invalid/expired
7.4 Functions
getKeycloakAdminClient()- Get authenticated admin clientgetUserById(userId)- Get user by Keycloak IDgetUserByEmail(email)- Get user by emailgetAllRoles()- Get all realm rolesgetUserRoles(userId)- Get user's role mappings
8. Security Considerations
8.1 Cookie Security
NextAuth Cookie Configuration (implicit):
- HttpOnly: Yes (prevents XSS access)
- Secure: Yes (if
NEXTAUTH_URLstarts withhttps://) - SameSite: Lax (default)
- Path:
/ - Domain: Dashboard domain
Keycloak Cookie Configuration (Keycloak-controlled):
- Set by Keycloak server
- Typically
SameSite=LaxorSameSite=None(for cross-site) - Domain: Keycloak domain or configured domain
8.2 Token Storage
- Access Token: Stored in encrypted JWT (server-side only accessible)
- Refresh Token: Stored in encrypted JWT
- Session Token: Encrypted JWT in HTTP-only cookie
Client-Side Access:
session.accessTokenis exposed to client viauseSession()- This is the Keycloak OAuth access token
- Can be used for API calls to Keycloak-protected resources
8.3 CORS & CSP
Content Security Policy (next.config.mjs):
'Content-Security-Policy': "frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net"
Allows framing from:
- Same origin (
'self') https://espace.slm-lab.nethttps://connect.slm-lab.net
8.4 Role-Based Access Control
Role Extraction:
- Roles come from Keycloak
realm_access.roles - Cleaned:
ROLE_prefix removed, lowercased - Stored in session:
session.user.role[]
Usage: Roles are available but not actively enforced in the codebase audit. Applications should implement RBAC checks.
9. Environment Variables
9.1 Required for Authentication
# Keycloak OAuth Configuration
KEYCLOAK_CLIENT_ID=neah-dashboard
KEYCLOAK_CLIENT_SECRET=<secret>
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/neah
KEYCLOAK_REALM=neah
# NextAuth Configuration
NEXTAUTH_URL=https://dashboard.example.com
NEXTAUTH_SECRET=<random-secret>
# Keycloak Admin (optional, for user management)
KEYCLOAK_ADMIN_USERNAME=admin
KEYCLOAK_ADMIN_PASSWORD=<password>
KEYCLOAK_BASE_URL=https://keycloak.example.com
9.2 Iframe Application URLs
All iframe applications require NEXT_PUBLIC_IFRAME_* environment variables:
NEXT_PUBLIC_IFRAME_PAROLE_URLNEXT_PUBLIC_IFRAME_AGILITY_URLNEXT_PUBLIC_IFRAME_AI_ASSISTANT_URLNEXT_PUBLIC_IFRAME_CONFERENCE_URL- ... (see section 4.2 for complete list)
10. Potential Issues & Recommendations
10.1 Token Expiration Bug
Location: app/api/auth/options.ts:176
if (Date.now() < (token.accessTokenExpires as number) * 1000) {
Issue: accessTokenExpires from Keycloak account.expires_at is already in seconds since epoch. Multiplying by 1000 assumes it's in milliseconds, which may cause incorrect expiration checks.
Recommendation: Verify Keycloak's expires_at format. If it's in seconds, remove the * 1000. If it's in milliseconds, keep it.
10.2 Cookie SameSite for Cross-Domain Iframes
Issue: If iframe applications are on different domains, Keycloak cookies may not be sent due to SameSite restrictions.
Recommendation:
- Configure Keycloak cookies with
SameSite=None; Secure - Ensure all domains use HTTPS
- Consider using a shared parent domain for cookies
10.3 Access Token Exposure
Issue: session.accessToken (Keycloak OAuth token) is exposed client-side.
Recommendation:
- Only expose if needed for client-side API calls
- Consider using proxy endpoints instead
- Implement token rotation if exposed
10.4 No Explicit Cookie Configuration
Issue: NextAuth cookie settings are implicit (defaults).
Recommendation: Explicitly configure cookies in authOptions:
cookies: {
sessionToken: {
name: `next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NEXTAUTH_URL?.startsWith('https://') ?? false,
}
}
}
10.5 Storage Initialization
Issue: Storage initialization happens client-side after authentication, which may cause race conditions.
Recommendation: Move storage initialization to server-side or use a more robust initialization pattern.
10.6 Service Token Invalidation Not Called
Issue: invalidateServiceTokens() exists but may not be called during sign-out.
Recommendation: Integrate service token invalidation into the sign-out flow.
11. Flow Diagrams
11.1 Complete Authentication Flow
User → /signin
→ signIn("keycloak")
→ /api/auth/signin/keycloak
→ Keycloak Authorization Endpoint
→ User Login (Keycloak)
→ Keycloak Token Endpoint
→ /api/auth/callback/keycloak
→ JWT Callback (store tokens)
→ Session Callback (build session)
→ Set Cookies
→ Redirect to /
→ Storage Init
→ Dashboard Loaded
11.2 Iframe SSO Flow
Dashboard (authenticated)
→ User clicks iframe app link
→ Server checks session (getServerSession)
→ If authenticated: Load iframe
→ Browser sends cookies to iframe domain
→ Iframe app reads Keycloak cookies
→ Iframe app validates session
→ Iframe app loads authenticated
11.3 Token Refresh Flow
Request with expired token
→ getServerSession()
→ Decrypt JWT
→ Check expiration
→ If expired: JWT Callback
→ refreshAccessToken()
→ POST to Keycloak /token
→ Get new tokens
→ Update JWT
→ Return session
12. File Reference Map
Core Authentication Files
| File | Purpose |
|---|---|
app/api/auth/[...nextauth]/route.ts |
NextAuth route handler |
app/api/auth/options.ts |
Main auth configuration |
app/signin/page.tsx |
Sign-in page |
app/signout/page.tsx |
Sign-out page |
components/auth/signout-handler.tsx |
Sign-out logic |
components/auth/auth-check.tsx |
Client-side auth guard |
lib/keycloak.ts |
Keycloak admin client |
lib/session.ts |
Session utilities |
types/next-auth.d.ts |
TypeScript definitions |
Iframe Application Files
All in app/*/page.tsx:
app/parole/page.tsxapp/agilite/page.tsxapp/alma/page.tsxapp/vision/page.tsx- ... (see section 4.2)
Supporting Files
| File | Purpose |
|---|---|
app/components/responsive-iframe.tsx |
Iframe component |
app/layout.tsx |
Root layout (session check) |
components/providers.tsx |
SessionProvider wrapper |
components/layout/layout-wrapper.tsx |
Layout wrapper with auth |
13. Testing Checklist
Authentication Flow
- Sign-in redirects to Keycloak
- Keycloak login works
- Callback receives tokens
- Session is created
- Cookies are set
- User redirected to dashboard
- Storage initializes
Session Management
- Session persists across page reloads
- Token refresh works when expired
- Session expires after 30 days
- Invalid tokens are rejected
Sign-Out
- Sign-out clears NextAuth cookies
- User redirected to sign-in
- Session invalidated
Iframe SSO
- Iframe apps receive Keycloak cookies
- Iframe apps authenticate automatically
- Cross-domain cookies work (if applicable)
- Unauthenticated users redirected
Security
- HttpOnly cookies enforced
- Secure cookies on HTTPS
- CSRF protection active
- Token encryption working
14. Conclusion
The authentication architecture uses a standard NextAuth + Keycloak OAuth 2.0 flow with JWT-based sessions. The system supports SSO for iframe applications via cookie sharing, assuming proper domain configuration.
Key Strengths:
- Standard OAuth 2.0/OpenID Connect implementation
- Stateless JWT sessions (scalable)
- Automatic token refresh
- Role-based user information
Areas for Improvement:
- Explicit cookie configuration
- Token expiration bug fix
- Service token invalidation integration
- Cross-domain cookie configuration verification
- Storage initialization robustness
Document Version: 1.0
Last Updated: 2024
Audited By: AI Assistant
Next Review: After implementing recommendations