685 lines
20 KiB
Markdown
685 lines
20 KiB
Markdown
# Améliorations du Flow de Connexion - Recommandations
|
|
|
|
## 📋 Vue d'ensemble
|
|
|
|
Ce document propose des améliorations concrètes pour corriger et optimiser le flow de connexion/déconnexion du dashboard Next.js avec NextAuth et Keycloak.
|
|
|
|
---
|
|
|
|
## 🎯 Problèmes Identifiés et Solutions
|
|
|
|
### Problème 1 : `prompt=login` toujours actif - Empêche SSO naturel
|
|
|
|
**Situation actuelle** :
|
|
```typescript
|
|
// app/api/auth/options.ts ligne 154
|
|
authorization: {
|
|
params: {
|
|
scope: "openid profile email roles",
|
|
prompt: "login" // ⚠️ TOUJOURS actif
|
|
}
|
|
}
|
|
```
|
|
|
|
**Impact** :
|
|
- ❌ L'utilisateur doit **toujours** saisir ses identifiants, même lors de la première visite
|
|
- ❌ Empêche l'expérience SSO naturelle
|
|
- ❌ Mauvaise UX pour les utilisateurs légitimes
|
|
|
|
**Solution recommandée** : Gérer `prompt=login` conditionnellement
|
|
|
|
```typescript
|
|
// app/api/auth/options.ts
|
|
authorization: {
|
|
params: {
|
|
scope: "openid profile email roles",
|
|
// Ne pas forcer prompt=login par défaut
|
|
// prompt: "login" // ❌ À SUPPRIMER
|
|
}
|
|
}
|
|
```
|
|
|
|
**ET** : Ajouter `prompt=login` uniquement après un logout explicite
|
|
|
|
```typescript
|
|
// Dans signIn() après logout
|
|
signIn("keycloak", {
|
|
callbackUrl: "/",
|
|
// Ajouter prompt=login uniquement si logout récent
|
|
...(shouldForceLogin ? { prompt: "login" } : {})
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Problème 2 : Détection session invalide trop complexe et fragile
|
|
|
|
**Situation actuelle** :
|
|
- Logique complexe dans `app/signin/page.tsx` (lignes 17-67)
|
|
- Vérification multiple de cookies, sessionStorage, URL params
|
|
- Race conditions possibles
|
|
- Auto-login peut se déclencher incorrectement
|
|
|
|
**Solution recommandée** : Simplifier avec un flag serveur
|
|
|
|
#### Option A : Utiliser un cookie serveur pour marquer le logout
|
|
|
|
```typescript
|
|
// app/api/auth/end-sso-session/route.ts
|
|
export async function POST(request: NextRequest) {
|
|
// ... code existant ...
|
|
|
|
// Après logout réussi, créer un cookie de flag
|
|
const response = NextResponse.json({ success: true });
|
|
response.cookies.set('force_login_prompt', 'true', {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
maxAge: 300 // 5 minutes
|
|
});
|
|
|
|
return response;
|
|
}
|
|
```
|
|
|
|
#### Option B : Utiliser un paramètre d'état dans l'URL Keycloak
|
|
|
|
```typescript
|
|
// Dans signout-handler.tsx, ajouter un paramètre state
|
|
const keycloakLogoutUrl = new URL(
|
|
`${keycloakIssuer}/protocol/openid-connect/logout`
|
|
);
|
|
keycloakLogoutUrl.searchParams.append('state', 'force_login');
|
|
// Keycloak renverra ce state dans le redirect
|
|
```
|
|
|
|
**Simplification de signin/page.tsx** :
|
|
|
|
```typescript
|
|
// Vérifier le cookie serveur au lieu de logique complexe
|
|
const forceLoginCookie = document.cookie
|
|
.split(';')
|
|
.find(c => c.trim().startsWith('force_login_prompt='));
|
|
|
|
if (forceLoginCookie) {
|
|
// Supprimer le cookie
|
|
document.cookie = 'force_login_prompt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
// Forcer prompt=login
|
|
signIn("keycloak", {
|
|
callbackUrl: "/",
|
|
// Ajouter prompt via custom params si possible
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Problème 3 : Cookies Keycloak non supprimables (domaine différent)
|
|
|
|
**Situation actuelle** :
|
|
- `clearKeycloakCookies()` échoue si Keycloak est sur un domaine différent
|
|
- Les cookies Keycloak persistent après logout
|
|
- Session SSO peut persister
|
|
|
|
**Solutions recommandées** :
|
|
|
|
#### Solution 1 : Utiliser Keycloak logout endpoint avec tous les paramètres
|
|
|
|
```typescript
|
|
// Améliorer le logout URL dans signout-handler.tsx
|
|
const keycloakLogoutUrl = new URL(
|
|
`${keycloakIssuer}/protocol/openid-connect/logout`
|
|
);
|
|
|
|
// Paramètres essentiels
|
|
keycloakLogoutUrl.searchParams.append('post_logout_redirect_uri',
|
|
window.location.origin + '/signin?logout=true');
|
|
keycloakLogoutUrl.searchParams.append('id_token_hint', idToken);
|
|
|
|
// ✅ AJOUTER ces paramètres pour forcer la suppression SSO
|
|
keycloakLogoutUrl.searchParams.append('kc_action', 'LOGOUT'); // Déjà présent
|
|
keycloakLogoutUrl.searchParams.append('logout_hint', 'true'); // Nouveau
|
|
|
|
// Si possible, utiliser le client_id pour forcer logout client
|
|
if (process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID) {
|
|
keycloakLogoutUrl.searchParams.append('client_id',
|
|
process.env.NEXT_PUBLIC_KEYcloak_CLIENT_ID);
|
|
}
|
|
```
|
|
|
|
#### Solution 2 : Utiliser Admin API pour terminer TOUTES les sessions
|
|
|
|
**Améliorer** `app/api/auth/end-sso-session/route.ts` :
|
|
|
|
```typescript
|
|
// Au lieu de logout({ id: userId }), utiliser logoutAllSessions
|
|
try {
|
|
// Option 1 : Logout toutes les sessions de l'utilisateur
|
|
await adminClient.users.logout({ id: userId });
|
|
|
|
// Option 2 : Si disponible, utiliser une méthode plus agressive
|
|
// Note: Vérifier la version de Keycloak Admin Client
|
|
// Certaines versions supportent logoutAllSessions
|
|
|
|
// Option 3 : Invalider les refresh tokens
|
|
const userSessions = await adminClient.users.listSessions({ id: userId });
|
|
for (const session of userSessions) {
|
|
await adminClient.users.logoutSession({
|
|
id: userId,
|
|
sessionId: session.id
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// ... gestion erreur
|
|
}
|
|
```
|
|
|
|
#### Solution 3 : Configurer Keycloak pour SameSite=None (si cross-domain)
|
|
|
|
**Configuration Keycloak** (à faire côté Keycloak) :
|
|
```
|
|
Cookie SameSite: None
|
|
Cookie Secure: true
|
|
Cookie Domain: .example.com (domaine parent partagé)
|
|
```
|
|
|
|
**Puis** améliorer `clearKeycloakCookies()` :
|
|
|
|
```typescript
|
|
// lib/session.ts
|
|
export function clearKeycloakCookies() {
|
|
const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
|
|
if (!keycloakIssuer) return;
|
|
|
|
try {
|
|
const keycloakUrl = new URL(keycloakIssuer);
|
|
const keycloakDomain = keycloakUrl.hostname;
|
|
|
|
// Extraire le domaine parent si possible
|
|
const domainParts = keycloakDomain.split('.');
|
|
const parentDomain = domainParts.length > 2
|
|
? '.' + domainParts.slice(-2).join('.')
|
|
: keycloakDomain;
|
|
|
|
const keycloakCookieNames = [
|
|
'KEYCLOAK_SESSION',
|
|
'KEYCLOAK_SESSION_LEGACY',
|
|
'KEYCLOAK_IDENTITY',
|
|
'KEYCLOAK_IDENTITY_LEGACY',
|
|
'AUTH_SESSION_ID',
|
|
'KC_RESTART',
|
|
'KC_RESTART_LEGACY'
|
|
];
|
|
|
|
// Essayer avec domaine parent (pour SameSite=None)
|
|
keycloakCookieNames.forEach(cookieName => {
|
|
// Avec domaine parent
|
|
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${parentDomain}; SameSite=None; Secure;`;
|
|
// Sans domaine (same-origin)
|
|
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=None; Secure;`;
|
|
// Avec path spécifique
|
|
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/realms/; domain=${parentDomain}; SameSite=None; Secure;`;
|
|
});
|
|
} catch (error) {
|
|
console.error('Error clearing Keycloak cookies:', error);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Problème 4 : Race condition entre logout et auto-login
|
|
|
|
**Situation actuelle** :
|
|
- Auto-login avec délai de 1 seconde
|
|
- Peut se déclencher pendant le flow de logout
|
|
- Flags `logout_in_progress` et `session_invalidated` peuvent être perdus
|
|
|
|
**Solution recommandée** : Utiliser un mécanisme plus robuste
|
|
|
|
#### Option A : Utiliser un cookie HttpOnly pour le flag
|
|
|
|
```typescript
|
|
// Créer une route API pour marquer le logout
|
|
// app/api/auth/mark-logout/route.ts
|
|
export async function POST() {
|
|
const response = NextResponse.json({ success: true });
|
|
response.cookies.set('logout_completed', 'true', {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
maxAge: 300 // 5 minutes
|
|
});
|
|
return response;
|
|
}
|
|
|
|
// Dans signout-handler.tsx
|
|
await fetch('/api/auth/mark-logout', { method: 'POST' });
|
|
|
|
// Dans signin/page.tsx
|
|
const logoutCompleted = document.cookie
|
|
.split(';')
|
|
.some(c => c.trim().startsWith('logout_completed='));
|
|
|
|
if (logoutCompleted) {
|
|
// Supprimer le cookie
|
|
document.cookie = 'logout_completed=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
// Ne pas auto-login
|
|
return;
|
|
}
|
|
```
|
|
|
|
#### Option B : Utiliser un état dans l'URL Keycloak callback
|
|
|
|
```typescript
|
|
// Lors du logout, ajouter un paramètre state
|
|
const logoutState = btoa(JSON.stringify({
|
|
logout: true,
|
|
timestamp: Date.now()
|
|
}));
|
|
|
|
// Keycloak renverra ce state dans le redirect
|
|
// Dans signin/page.tsx, vérifier le state
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const state = urlParams.get('state');
|
|
if (state) {
|
|
try {
|
|
const stateData = JSON.parse(atob(state));
|
|
if (stateData.logout) {
|
|
// Ne pas auto-login
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// State invalide, ignorer
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Problème 5 : Configuration cookies NextAuth non explicite
|
|
|
|
**Situation actuelle** :
|
|
- Pas de configuration explicite des cookies NextAuth
|
|
- Utilise les valeurs par défaut
|
|
- Pas de contrôle sur SameSite, Secure, etc.
|
|
|
|
**Solution recommandée** : Configurer explicitement les cookies
|
|
|
|
```typescript
|
|
// app/api/auth/options.ts
|
|
export const authOptions: NextAuthOptions = {
|
|
// ... providers ...
|
|
|
|
// ✅ AJOUTER configuration explicite des cookies
|
|
cookies: {
|
|
sessionToken: {
|
|
name: `next-auth.session-token`,
|
|
options: {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
secure: process.env.NEXTAUTH_URL?.startsWith('https://') ?? false,
|
|
// Domaine explicite si nécessaire
|
|
// domain: process.env.COOKIE_DOMAIN,
|
|
},
|
|
},
|
|
callbackUrl: {
|
|
name: `next-auth.callback-url`,
|
|
options: {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
secure: process.env.NEXTAUTH_URL?.startsWith('https://') ?? false,
|
|
},
|
|
},
|
|
csrfToken: {
|
|
name: `next-auth.csrf-token`,
|
|
options: {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
secure: process.env.NEXTAUTH_URL?.startsWith('https://') ?? false,
|
|
},
|
|
},
|
|
state: {
|
|
name: `next-auth.state`,
|
|
options: {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
secure: process.env.NEXTAUTH_URL?.startsWith('https://') ?? false,
|
|
},
|
|
},
|
|
},
|
|
|
|
// ... reste de la config ...
|
|
};
|
|
```
|
|
|
|
**Avantages** :
|
|
- Contrôle total sur les cookies
|
|
- Peut ajuster SameSite pour cross-domain si nécessaire
|
|
- Meilleure sécurité
|
|
|
|
---
|
|
|
|
### Problème 6 : Détection session invalide côté client uniquement
|
|
|
|
**Situation actuelle** :
|
|
- Détection session invalide uniquement côté client
|
|
- Vérification de cookies via `document.cookie`
|
|
- Peut être contourné ou mal interprété
|
|
|
|
**Solution recommandée** : Détection serveur + client
|
|
|
|
#### Créer une route API pour vérifier l'état de session
|
|
|
|
```typescript
|
|
// app/api/auth/session-status/route.ts
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth/next';
|
|
import { authOptions } from '../options';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
// Vérifier si la session existe mais est invalide
|
|
const hasSessionCookie = request.cookies.has('next-auth.session-token') ||
|
|
request.cookies.has('__Secure-next-auth.session-token') ||
|
|
request.cookies.has('__Host-next-auth.session-token');
|
|
|
|
return NextResponse.json({
|
|
hasSession: !!session,
|
|
hasSessionCookie,
|
|
isInvalid: hasSessionCookie && !session, // Cookie existe mais session invalide
|
|
shouldForceLogin: request.cookies.get('force_login_prompt')?.value === 'true',
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Utiliser cette route dans signin/page.tsx
|
|
|
|
```typescript
|
|
// app/signin/page.tsx
|
|
useEffect(() => {
|
|
const checkSessionStatus = async () => {
|
|
const response = await fetch('/api/auth/session-status');
|
|
const status = await response.json();
|
|
|
|
if (status.isInvalid) {
|
|
// Session invalidée, ne pas auto-login
|
|
sessionStorage.setItem('session_invalidated', 'true');
|
|
return;
|
|
}
|
|
|
|
if (status.shouldForceLogin) {
|
|
// Forcer prompt=login
|
|
signIn("keycloak", {
|
|
callbackUrl: "/",
|
|
// prompt: "login" via custom params si possible
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Nouvel utilisateur, auto-login OK
|
|
if (status === "unauthenticated" && !status.hasSessionCookie) {
|
|
signIn("keycloak", { callbackUrl: "/" });
|
|
}
|
|
};
|
|
|
|
checkSessionStatus();
|
|
}, []);
|
|
```
|
|
|
|
---
|
|
|
|
### Problème 7 : Gestion d'erreur token refresh insuffisante
|
|
|
|
**Situation actuelle** :
|
|
- `refreshAccessToken()` détecte `SessionNotActive`
|
|
- Retourne `error: "SessionNotActive"`
|
|
- Mais la gestion peut être améliorée
|
|
|
|
**Solution recommandée** : Améliorer la gestion d'erreur
|
|
|
|
```typescript
|
|
// app/api/auth/options.ts
|
|
async function refreshAccessToken(token: ExtendedJWT) {
|
|
try {
|
|
const response = await fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token`, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
client_id: process.env.KEYCLOAK_CLIENT_ID!,
|
|
client_secret: process.env.KEYCLOAK_CLIENT_SECRET!,
|
|
grant_type: "refresh_token",
|
|
refresh_token: token.refreshToken || '',
|
|
}),
|
|
method: "POST",
|
|
});
|
|
|
|
const refreshedTokens = await response.json();
|
|
|
|
if (!response.ok) {
|
|
// ✅ AMÉLIORATION : Détecter différents types d'erreurs
|
|
const errorType = refreshedTokens.error;
|
|
const errorDescription = refreshedTokens.error_description || '';
|
|
|
|
// Session invalide (logout depuis iframe ou Keycloak)
|
|
if (errorType === 'invalid_grant' ||
|
|
errorDescription.includes('Session not active') ||
|
|
errorDescription.includes('Token is not active') ||
|
|
errorDescription.includes('Session expired')) {
|
|
console.log("Keycloak session invalidated, marking for removal");
|
|
return {
|
|
...token,
|
|
error: "SessionNotActive",
|
|
// ✅ Supprimer tous les tokens
|
|
accessToken: undefined,
|
|
refreshToken: undefined,
|
|
idToken: undefined,
|
|
};
|
|
}
|
|
|
|
// Refresh token expiré (inactivité prolongée)
|
|
if (errorType === 'invalid_grant' &&
|
|
errorDescription.includes('Refresh token expired')) {
|
|
console.log("Refresh token expired, user needs to re-authenticate");
|
|
return {
|
|
...token,
|
|
error: "RefreshTokenExpired",
|
|
accessToken: undefined,
|
|
refreshToken: undefined,
|
|
idToken: undefined,
|
|
};
|
|
}
|
|
|
|
// Autre erreur
|
|
throw refreshedTokens;
|
|
}
|
|
|
|
return {
|
|
...token,
|
|
accessToken: refreshedTokens.access_token,
|
|
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
|
|
idToken: token.idToken, // Keycloak ne renvoie pas de nouvel ID token
|
|
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
|
|
error: undefined, // ✅ Clear any previous errors
|
|
};
|
|
} catch (error: any) {
|
|
console.error("Error refreshing access token:", error);
|
|
|
|
// ✅ AMÉLIORATION : Gestion d'erreur plus robuste
|
|
if (error?.error === 'invalid_grant' ||
|
|
error?.error_description?.includes('Session not active') ||
|
|
error?.error_description?.includes('Token is not active')) {
|
|
return {
|
|
...token,
|
|
error: "SessionNotActive",
|
|
accessToken: undefined,
|
|
refreshToken: undefined,
|
|
idToken: undefined,
|
|
};
|
|
}
|
|
|
|
return {
|
|
...token,
|
|
error: "RefreshAccessTokenError",
|
|
accessToken: undefined,
|
|
refreshToken: undefined,
|
|
idToken: undefined,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Plan d'Implémentation Recommandé
|
|
|
|
### Phase 1 : Corrections Critiques (Priorité Haute)
|
|
|
|
1. **Supprimer `prompt=login` par défaut**
|
|
- Fichier : `app/api/auth/options.ts`
|
|
- Impact : Améliore l'UX pour les utilisateurs légitimes
|
|
|
|
2. **Améliorer la gestion d'erreur token refresh**
|
|
- Fichier : `app/api/auth/options.ts`
|
|
- Impact : Meilleure détection des sessions invalides
|
|
|
|
3. **Configurer explicitement les cookies NextAuth**
|
|
- Fichier : `app/api/auth/options.ts`
|
|
- Impact : Meilleur contrôle et sécurité
|
|
|
|
### Phase 2 : Améliorations Logout (Priorité Moyenne)
|
|
|
|
4. **Créer route API pour marquer logout**
|
|
- Nouveau fichier : `app/api/auth/mark-logout/route.ts`
|
|
- Modifier : `components/auth/signout-handler.tsx`
|
|
- Modifier : `app/signin/page.tsx`
|
|
- Impact : Élimine les race conditions
|
|
|
|
5. **Améliorer Keycloak logout URL**
|
|
- Modifier : `components/auth/signout-handler.tsx`
|
|
- Modifier : `components/main-nav.tsx`
|
|
- Modifier : `components/layout/layout-wrapper.tsx`
|
|
- Impact : Meilleure suppression des sessions SSO
|
|
|
|
6. **Améliorer `clearKeycloakCookies()`**
|
|
- Modifier : `lib/session.ts`
|
|
- Impact : Meilleure tentative de suppression cookies cross-domain
|
|
|
|
### Phase 3 : Optimisations (Priorité Basse)
|
|
|
|
7. **Créer route API pour vérifier statut session**
|
|
- Nouveau fichier : `app/api/auth/session-status/route.ts`
|
|
- Modifier : `app/signin/page.tsx`
|
|
- Impact : Détection plus fiable
|
|
|
|
8. **Simplifier logique signin/page.tsx**
|
|
- Modifier : `app/signin/page.tsx`
|
|
- Impact : Code plus maintenable
|
|
|
|
---
|
|
|
|
## 📝 Checklist d'Implémentation
|
|
|
|
### Étape 1 : Configuration NextAuth
|
|
- [ ] Supprimer `prompt: "login"` par défaut dans `options.ts`
|
|
- [ ] Ajouter configuration explicite des cookies
|
|
- [ ] Améliorer gestion d'erreur `refreshAccessToken()`
|
|
|
|
### Étape 2 : Amélioration Logout
|
|
- [ ] Créer route `/api/auth/mark-logout`
|
|
- [ ] Modifier `signout-handler.tsx` pour utiliser la route
|
|
- [ ] Améliorer Keycloak logout URL avec tous les paramètres
|
|
- [ ] Améliorer `clearKeycloakCookies()` pour cross-domain
|
|
|
|
### Étape 3 : Amélioration Signin
|
|
- [ ] Créer route `/api/auth/session-status`
|
|
- [ ] Simplifier logique dans `signin/page.tsx`
|
|
- [ ] Utiliser cookie serveur pour détecter logout
|
|
- [ ] Ajouter `prompt=login` conditionnel après logout
|
|
|
|
### Étape 4 : Tests
|
|
- [ ] Tester login normal (première visite)
|
|
- [ ] Tester login après logout (doit demander credentials)
|
|
- [ ] Tester logout depuis dashboard
|
|
- [ ] Tester logout depuis iframe
|
|
- [ ] Tester expiration session
|
|
- [ ] Tester refresh token expiré
|
|
|
|
---
|
|
|
|
## 🔧 Configuration Keycloak Recommandée
|
|
|
|
### Paramètres Client OAuth
|
|
|
|
1. **Valid Redirect URIs** :
|
|
```
|
|
http://localhost:3000/api/auth/callback/keycloak
|
|
https://your-domain.com/api/auth/callback/keycloak
|
|
```
|
|
|
|
2. **Web Origins** :
|
|
```
|
|
http://localhost:3000
|
|
https://your-domain.com
|
|
```
|
|
|
|
3. **Post Logout Redirect URIs** :
|
|
```
|
|
http://localhost:3000/signin?logout=true
|
|
https://your-domain.com/signin?logout=true
|
|
```
|
|
|
|
4. **Access Token Lifespan** : 5 minutes (recommandé)
|
|
5. **SSO Session Idle** : 30 minutes
|
|
6. **SSO Session Max** : 10 heures
|
|
|
|
### Configuration Realm
|
|
|
|
1. **Cookies** :
|
|
- SameSite: `None` (si cross-domain)
|
|
- Secure: `true` (si HTTPS)
|
|
- Domain: Domaine parent partagé (si cross-domain)
|
|
|
|
2. **Session Management** :
|
|
- Enable SSO Session Idle: `true`
|
|
- SSO Session Idle Timeout: 30 minutes
|
|
- SSO Session Max Lifespan: 10 heures
|
|
|
|
---
|
|
|
|
## 📊 Métriques de Succès
|
|
|
|
Après implémentation, vérifier :
|
|
|
|
1. ✅ **Login première visite** : SSO fonctionne (pas de prompt si session Keycloak existe)
|
|
2. ✅ **Login après logout** : Prompt credentials demandé
|
|
3. ✅ **Logout dashboard** : Toutes les sessions supprimées
|
|
4. ✅ **Logout iframe** : Dashboard se déconnecte automatiquement
|
|
5. ✅ **Expiration session** : Redirection vers signin propre
|
|
6. ✅ **Refresh token expiré** : Redirection vers signin avec message approprié
|
|
|
|
---
|
|
|
|
## 🎯 Résumé des Améliorations
|
|
|
|
| Problème | Solution | Priorité | Fichiers Impactés |
|
|
|----------|----------|----------|-------------------|
|
|
| `prompt=login` toujours actif | Gérer conditionnellement | Haute | `options.ts`, `signin/page.tsx` |
|
|
| Détection session invalide complexe | Cookie serveur + route API | Haute | `signin/page.tsx`, `end-sso-session/route.ts` |
|
|
| Cookies Keycloak non supprimables | Améliorer logout URL + Admin API | Moyenne | `signout-handler.tsx`, `session.ts` |
|
|
| Race condition logout/login | Cookie HttpOnly pour flag | Moyenne | `signout-handler.tsx`, `signin/page.tsx` |
|
|
| Config cookies non explicite | Configuration explicite | Haute | `options.ts` |
|
|
| Gestion erreur refresh | Améliorer détection erreurs | Haute | `options.ts` |
|
|
|
|
---
|
|
|
|
**Document créé le** : $(date)
|
|
**Dernière mise à jour** : Recommandations d'amélioration du flow de connexion
|
|
|