260 lines
8.4 KiB
Markdown
260 lines
8.4 KiB
Markdown
# Iframe Logout Auto-Login Issue Analysis
|
|
|
|
## Problem
|
|
|
|
When you log out from an iframe application, you are automatically logged back into the dashboard without being prompted for credentials.
|
|
|
|
## Flow Trace
|
|
|
|
### Scenario: User Logs Out from Iframe Application
|
|
|
|
#### Step 1: Iframe Application Logout
|
|
```
|
|
Location: Iframe application (e.g., /parole, /gite, etc.)
|
|
Action: User clicks logout in iframe
|
|
|
|
What happens:
|
|
- Iframe app may call Keycloak logout endpoint directly
|
|
- OR: Iframe app sends postMessage to parent: { type: 'KEYCLOAK_LOGOUT' }
|
|
- OR: Iframe app clears its own session cookies
|
|
```
|
|
|
|
#### Step 2A: If Iframe Sends PostMessage (Expected Flow)
|
|
```
|
|
Location: components/layout/layout-wrapper.tsx (line 26-106)
|
|
OR: app/components/responsive-iframe.tsx (line 110-153)
|
|
|
|
Action: Dashboard receives logout message
|
|
|
|
What happens:
|
|
1. Sets sessionStorage.setItem('just_logged_out', 'true')
|
|
2. Sets document.cookie = 'logout_in_progress=true; path=/; max-age=60'
|
|
3. Calls /api/auth/end-sso-session (Admin API)
|
|
4. Calls signOut() from NextAuth
|
|
5. Redirects to Keycloak logout endpoint
|
|
6. Keycloak redirects back to /signin?logout=true
|
|
```
|
|
|
|
#### Step 2B: If Iframe Calls Keycloak Logout Directly (Actual Flow - Problem)
|
|
```
|
|
Location: Iframe application
|
|
|
|
Action: Iframe calls Keycloak logout endpoint directly
|
|
|
|
What happens:
|
|
1. Iframe redirects to: ${KEYCLOAK_ISSUER}/protocol/openid-connect/logout
|
|
2. Keycloak clears session cookies
|
|
3. Keycloak may redirect iframe back to its own logout page
|
|
4. Dashboard doesn't know about this logout
|
|
5. Dashboard still has NextAuth session (valid for 30 days)
|
|
```
|
|
|
|
#### Step 3: Dashboard Detects Session Invalidation
|
|
```
|
|
Location: app/api/auth/options.ts (refreshAccessToken function)
|
|
|
|
When: NextAuth tries to refresh the access token
|
|
|
|
What happens:
|
|
1. Dashboard calls Keycloak token refresh endpoint
|
|
2. Keycloak returns: { error: 'invalid_grant', error_description: 'Session not active' }
|
|
3. refreshAccessToken detects this error (line 100-108)
|
|
4. Returns token with error: "SessionNotActive"
|
|
5. JWT callback clears tokens (line 248-256)
|
|
6. Session callback returns null (line 272-276)
|
|
7. NextAuth treats user as unauthenticated
|
|
8. Status becomes "unauthenticated"
|
|
```
|
|
|
|
#### Step 4: Sign-In Page Auto-Login (THE PROBLEM)
|
|
```
|
|
Location: app/signin/page.tsx (line 47-79)
|
|
|
|
When: User is redirected to /signin (or status becomes "unauthenticated")
|
|
|
|
What happens:
|
|
1. Component mounts
|
|
2. First useEffect (line 16-45) checks for logout flag
|
|
- If logout=true in URL, sets isLogoutRedirect.current = true
|
|
- Removes 'just_logged_out' from sessionStorage
|
|
3. Second useEffect (line 47-79) checks authentication status
|
|
- If status === "authenticated" → redirects to home ✅
|
|
- If status === "unauthenticated" → triggers auto-login ❌
|
|
|
|
THE PROBLEM:
|
|
- When iframe logs out directly (not via postMessage), dashboard doesn't set logout flags
|
|
- Status becomes "unauthenticated" (because Keycloak session was cleared)
|
|
- Sign-in page sees status === "unauthenticated"
|
|
- Auto-login logic triggers after 1 second (line 69)
|
|
- signIn("keycloak") is called
|
|
- Keycloak still has SSO session cookie (if it wasn't fully cleared)
|
|
- User is auto-authenticated without credentials ❌
|
|
```
|
|
|
|
## Root Cause Analysis
|
|
|
|
### Problem 1: Missing Logout Flags
|
|
|
|
**When iframe logs out directly (not via postMessage):**
|
|
- Dashboard doesn't know about the logout
|
|
- `just_logged_out` is NOT set in sessionStorage
|
|
- `logout_in_progress` cookie is NOT set
|
|
- Sign-in page doesn't know this is a logout scenario
|
|
|
|
**Result**: Sign-in page treats it as a normal "unauthenticated" state and triggers auto-login.
|
|
|
|
### Problem 2: Auto-Login Logic Timing
|
|
|
|
**Sign-in page auto-login logic** (`app/signin/page.tsx:66-78`):
|
|
```typescript
|
|
if (status === "unauthenticated") {
|
|
hasAttemptedLogin.current = true;
|
|
const timer = setTimeout(() => {
|
|
if (!isLogoutRedirect.current) {
|
|
signIn("keycloak", { callbackUrl: "/" });
|
|
}
|
|
}, 1000);
|
|
}
|
|
```
|
|
|
|
**The Issue**:
|
|
- `isLogoutRedirect.current` is set in the first useEffect (line 16-45)
|
|
- But it only checks for `logout=true` in URL or `just_logged_out` in sessionStorage
|
|
- If iframe logs out directly, neither of these is set
|
|
- After 1 second, auto-login triggers
|
|
- `isLogoutRedirect.current` is still `false` (because logout flags weren't set)
|
|
- `signIn("keycloak")` is called
|
|
- User is auto-authenticated
|
|
|
|
### Problem 3: SSO Session Cookie Persistence
|
|
|
|
**Even if logout flags are set correctly:**
|
|
- Keycloak SSO session cookie (`KEYCLOAK_SESSION`) may still exist
|
|
- When `signIn("keycloak")` is called, Keycloak checks for SSO session cookie
|
|
- If cookie exists, Keycloak auto-authenticates without credentials
|
|
- This happens even with `prompt=login` parameter (if SSO session is still valid)
|
|
|
|
## Why This Happens
|
|
|
|
### Flow 1: Iframe Logs Out via PostMessage (Works Correctly)
|
|
```
|
|
1. Iframe sends postMessage → Dashboard receives it
|
|
2. Dashboard sets logout flags ✅
|
|
3. Dashboard calls logout endpoints ✅
|
|
4. Redirects to /signin?logout=true ✅
|
|
5. Sign-in page sees logout=true ✅
|
|
6. Auto-login is prevented ✅
|
|
7. User must click "Se connecter" manually ✅
|
|
```
|
|
|
|
### Flow 2: Iframe Logs Out Directly (THE PROBLEM)
|
|
```
|
|
1. Iframe calls Keycloak logout directly
|
|
2. Keycloak clears session cookies
|
|
3. Dashboard doesn't know about logout ❌
|
|
4. NextAuth tries to refresh token
|
|
5. Keycloak returns "Session not active"
|
|
6. NextAuth marks user as unauthenticated
|
|
7. User is redirected to /signin (no logout=true) ❌
|
|
8. Sign-in page sees status="unauthenticated" ❌
|
|
9. Auto-login triggers after 1 second ❌
|
|
10. Keycloak still has SSO session cookie ❌
|
|
11. User is auto-authenticated ❌
|
|
```
|
|
|
|
## The Real Issue
|
|
|
|
**The sign-in page auto-login logic is too aggressive:**
|
|
|
|
1. It triggers auto-login for ANY "unauthenticated" state
|
|
2. It doesn't distinguish between:
|
|
- User never logged in (should auto-login) ✅
|
|
- User logged out (should NOT auto-login) ❌
|
|
- Session expired (should NOT auto-login) ❌
|
|
- Keycloak session invalidated (should NOT auto-login) ❌
|
|
|
|
3. The logout detection only works if:
|
|
- `logout=true` is in URL (from Keycloak redirect)
|
|
- `just_logged_out` is in sessionStorage (from dashboard logout)
|
|
- But NOT if iframe logs out directly
|
|
|
|
## Solution Requirements
|
|
|
|
To fix this issue, you need to:
|
|
|
|
1. **Detect Keycloak Session Invalidation**:
|
|
- When NextAuth detects "SessionNotActive" error
|
|
- Set a flag to prevent auto-login
|
|
- Mark this as a logout scenario, not a new login
|
|
|
|
2. **Improve Logout Detection**:
|
|
- Check for Keycloak session cookie existence
|
|
- If session was invalidated (not just expired), prevent auto-login
|
|
- Store logout reason in sessionStorage
|
|
|
|
3. **Modify Auto-Login Logic**:
|
|
- Only auto-login if:
|
|
- User is truly unauthenticated (never logged in)
|
|
- AND no logout flags are set
|
|
- AND no session invalidation detected
|
|
- Don't auto-login if:
|
|
- Logout flags are set
|
|
- Session was invalidated
|
|
- User came from a logout flow
|
|
|
|
4. **Handle Iframe Direct Logout**:
|
|
- Detect when Keycloak session is invalidated
|
|
- Set logout flags automatically
|
|
- Prevent auto-login
|
|
|
|
## Current Code Issues
|
|
|
|
### Issue 1: Auto-Login Logic (`app/signin/page.tsx:66-78`)
|
|
```typescript
|
|
if (status === "unauthenticated") {
|
|
// This triggers for ANY unauthenticated state
|
|
// Doesn't check if session was invalidated
|
|
signIn("keycloak", { callbackUrl: "/" });
|
|
}
|
|
```
|
|
|
|
### Issue 2: Logout Detection (`app/signin/page.tsx:16-45`)
|
|
```typescript
|
|
// Only checks for explicit logout flags
|
|
// Doesn't detect session invalidation
|
|
const logoutParam = searchParams.get('logout');
|
|
const fromLogout = sessionStorage.getItem('just_logged_out');
|
|
```
|
|
|
|
### Issue 3: Session Invalidation Detection (`app/api/auth/options.ts:248-256`)
|
|
```typescript
|
|
// Detects session invalidation
|
|
// But doesn't set logout flags
|
|
// Sign-in page doesn't know session was invalidated
|
|
if (refreshedToken.error === "SessionNotActive") {
|
|
return {
|
|
...refreshedToken,
|
|
accessToken: undefined,
|
|
// Should set a flag here to prevent auto-login
|
|
};
|
|
}
|
|
```
|
|
|
|
## Summary
|
|
|
|
**Why you're auto-logged in after iframe logout:**
|
|
|
|
1. Iframe logs out directly (not via postMessage)
|
|
2. Keycloak session is cleared
|
|
3. Dashboard detects session invalidation
|
|
4. User becomes "unauthenticated"
|
|
5. Sign-in page auto-login logic triggers (after 1 second)
|
|
6. Keycloak still has SSO session cookie
|
|
7. User is auto-authenticated without credentials
|
|
|
|
**The fix requires:**
|
|
- Detecting session invalidation and setting logout flags
|
|
- Preventing auto-login when session was invalidated
|
|
- Only auto-login for truly new users (never logged in)
|
|
|