NeahNew/AUTHENTICATION_FLOW_AUDIT.md
2026-01-01 17:40:16 +01:00

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

  1. NextAuth.js - Authentication framework
  2. Keycloak - Identity Provider (IdP) via OAuth 2.0/OpenID Connect
  3. JWT Strategy - Session management (no database sessions)
  4. Iframe Applications - Multiple embedded applications using SSO via cookies
  5. 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 in
  • GET/POST /api/auth/signout - Sign out
  • GET /api/auth/session - Get current session
  • GET /api/auth/csrf - CSRF token
  • GET /api/auth/providers - Available providers
  • GET /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 identifier
  • KEYCLOAK_CLIENT_SECRET - OAuth client secret
  • KEYCLOAK_ISSUER - Keycloak realm issuer URL (e.g., https://keycloak.example.com/realms/neah)

OAuth Scopes Requested:

  • openid - OpenID Connect core
  • profile - User profile information
  • email - User email address
  • roles - 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:

  1. Receives Keycloak profile with realm_access.roles
  2. Extracts roles from realm_access.roles array
  3. Cleans roles by:
    • Removing ROLE_ prefix (if present)
    • Converting to lowercase
  4. Maps Keycloak profile fields to NextAuth user:
    • subid
    • name or preferred_usernamename
    • emailemail
    • given_namefirst_name
    • family_namelast_name
    • preferred_usernameusername
    • 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:

  1. Parole (/parole) - NEXT_PUBLIC_IFRAME_PAROLE_URL
  2. Agilite (/agilite) - NEXT_PUBLIC_IFRAME_AGILITY_URL
  3. Alma (/alma) - NEXT_PUBLIC_IFRAME_AI_ASSISTANT_URL
  4. Vision (/vision) - NEXT_PUBLIC_IFRAME_CONFERENCE_URL
  5. The Message (/the-message) - NEXT_PUBLIC_IFRAME_THEMESSAGE_URL
  6. WP Admin (/wp-admin) - NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL
  7. Mediation (/mediation) - NEXT_PUBLIC_IFRAME_MEDIATIONS_URL
  8. Apprendre (/apprendre) - NEXT_PUBLIC_IFRAME_LEARN_URL
  9. Gite (/gite) - NEXT_PUBLIC_IFRAME_GITE_URL
  10. Artlab (/artlab) - NEXT_PUBLIC_IFRAME_ARTLAB_URL
  11. Calcul (/calcul) - NEXT_PUBLIC_IFRAME_CALCULATION_URL
  12. Chapitre (/chapitre) - NEXT_PUBLIC_IFRAME_CHAPTER_URL
  13. Dossiers (/dossiers) - NEXT_PUBLIC_IFRAME_DRIVE_URL
  14. CRM (/crm) - NEXT_PUBLIC_IFRAME_MEDIATIONS_URL
  15. Livres (/livres) - NEXT_PUBLIC_IFRAME_LIVRE_URL
  16. Showcase (/showcase) - NEXT_PUBLIC_IFRAME_SHOWCASE_URL
  17. Radio (/radio) - NEXT_PUBLIC_IFRAME_RADIO_URL
  18. Press (/press) - NEXT_PUBLIC_IFRAME_SHOWCASE_URL
  19. Observatory - NEXT_PUBLIC_IFRAME_OBSERVATORY_URL
  20. Time Tracker - NEXT_PUBLIC_IFRAME_TIMETRACKER_URL
  21. Missions Board - NEXT_PUBLIC_IFRAME_MISSIONSBOARD_URL
  22. Carnet - NEXT_PUBLIC_IFRAME_CARNET_URL

How It Works:

  1. Parent Dashboard Authentication:

    • User authenticates via Keycloak in the dashboard
    • Keycloak sets authentication cookies (domain: Keycloak domain)
    • NextAuth sets session cookies (domain: dashboard domain)
  2. 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
  3. 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; Secure cookie attributes for cross-site

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

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:

  1. Reads next-auth.session-token cookie from request
  2. Decrypts JWT using NEXTAUTH_SECRET
  3. Validates token signature and expiration
  4. If expired, triggers refresh (via JWT callback)
  5. 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:

  1. useSession() hook calls /api/auth/session
  2. Server decrypts JWT and returns session
  3. Client receives session object
  4. Automatically refetches when token refreshes

Location: Used in:

  • app/signin/page.tsx
  • components/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:

  1. Client Credentials (Preferred):

    grant_type: 'client_credentials'
    client_id: KEYCLOAK_CLIENT_ID
    client_secret: KEYCLOAK_CLIENT_SECRET
    
  2. 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 client
  • getUserById(userId) - Get user by Keycloak ID
  • getUserByEmail(email) - Get user by email
  • getAllRoles() - Get all realm roles
  • getUserRoles(userId) - Get user's role mappings

8. Security Considerations

NextAuth Cookie Configuration (implicit):

  • HttpOnly: Yes (prevents XSS access)
  • Secure: Yes (if NEXTAUTH_URL starts with https://)
  • SameSite: Lax (default)
  • Path: /
  • Domain: Dashboard domain

Keycloak Cookie Configuration (Keycloak-controlled):

  • Set by Keycloak server
  • Typically SameSite=Lax or SameSite=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.accessToken is exposed to client via useSession()
  • 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.net
  • https://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_URL
  • NEXT_PUBLIC_IFRAME_AGILITY_URL
  • NEXT_PUBLIC_IFRAME_AI_ASSISTANT_URL
  • NEXT_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.

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

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.tsx
  • app/agilite/page.tsx
  • app/alma/page.tsx
  • app/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