Authentification
Contexte utilisateur — Cache & Invalidation
Quand appeler /me, stratégie de cache React, invalidation après refresh token et bonnes pratiques.
Version v1.04 min de lecture
Contexte utilisateur — Cache & Invalidation
Ce chapitre couvre la stratégie de mise en cache du payload
/meet les patterns d'invalidation. Lisez Structure du payload /me d'abord.
Quand appeler /me
| Situation | Appeler /me ? |
|---|---|
| Au login (après enrichissement) | Oui — stocker dans le context |
| Après un refresh token | Oui — les droits peuvent avoir changé |
| À chaque navigation | Non — utiliser les données en cache |
| Après un changement de profil (si l'admin a modifié les droits) | Oui — invalider le cache et re-fetcher |
| Après un logout partiel ou changement d'organisation | Oui |
Pattern React recommandé — AuthContext
typescript// lib/auth-context.tsx import { createContext, useContext, useState, useCallback } from "react"; interface MeUser { id: string; wid: string; email: string; firstName: string; lastName: string; adminLevel: string; wakaRoles: string[]; appRights: string[]; features: string[]; customer: { id: string; wid: string; name: string }; // ... autres champs } interface AuthContextValue { user: MeUser | null; loading: boolean; refresh: () => Promise<void>; } const AuthContext = createContext<AuthContextValue>({ user: null, loading: false, refresh: async () => {}, }); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<MeUser | null>(null); const [loading, setLoading] = useState(false); const refresh = useCallback(async () => { setLoading(true); try { const res = await fetch("/api/proxy/me"); // via votre proxy serveur if (res.ok) setUser(await res.json()); } finally { setLoading(false); } }, []); return ( <AuthContext.Provider value={{ user, loading, refresh }}> {children} </AuthContext.Provider> ); } export const useAuth = () => useContext(AuthContext);
Mise en cache et TTL recommandé
/me est appelé une fois au login. La réponse est stable tant que :
- Les droits de l'utilisateur n'ont pas été modifiés par un admin.
- Le token n'a pas été rafraîchi.
Stratégie recommandée :
- Stocker dans un React context (en mémoire, pas en localStorage).
- Invalider le cache après chaque refresh token (les droits peuvent avoir changé).
- Exposer un hook
useAuth().refresh()pour forcer le rechargement si l'admin notifie un changement.
Synchronisation après refresh token
typescript// app/api/auth/refresh/route.ts export async function POST(req: NextRequest) { const cookieStore = await cookies(); const refreshToken = cookieStore.get("refresh_token")?.value; if (!refreshToken) { return NextResponse.json({ error: "No refresh token" }, { status: 401 }); } // 1. Rafraîchir les tokens Keycloak const refreshResponse = await fetch(`${BFF_URL}/api/auth/token/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken }), }); if (!refreshResponse.ok) { // Supprimer les cookies et forcer le re-login cookieStore.delete("keycloak_token"); cookieStore.delete("wakastart_token"); cookieStore.delete("refresh_token"); return NextResponse.json({ error: "Refresh failed" }, { status: 401 }); } const { access_token, refresh_token: newRefreshToken } = await refreshResponse.json(); // 2. Re-enrichir le nouveau token const enrichResponse = await fetch(`${BFF_URL}/api/auth/enrich`, { method: "POST", headers: { Authorization: `Bearer ${access_token}` }, }); const { token: wakaToken } = await enrichResponse.json(); // 3. Mettre à jour les cookies cookieStore.set("keycloak_token", access_token, { httpOnly: true, sameSite: "lax" }); cookieStore.set("wakastart_token", wakaToken, { httpOnly: true, sameSite: "lax" }); cookieStore.set("refresh_token", newRefreshToken, { httpOnly: true, sameSite: "lax" }); // 4. Signaler au client de re-fetcher /me return NextResponse.json({ refreshed: true }); }
Côté client, après un refresh réussi :
typescriptconst { refresh } = useAuth(); // Après chaque refresh token réussi await refresh(); // re-fetch /me avec les nouveaux droits
Bonnes pratiques
- Ne stockez pas le payload
/medanslocalStorage— il contient des droits sensibles. - Utilisez les
appRightsuniquement pour l'affichage conditionnel côté front. Le backend est la source d'autorité. - Si
adminLevel === "None"etappRights.length === 0, l'utilisateur n'a aucun droit — affichez une page dédiée. - Vérifiez
isActiveau chargement : sifalse, redirigez vers une page "Compte désactivé". - Utilisez
featurespour masquer des sections entières (ex: si"invitations"absent, masquer tout le module invitations).
Pièges classiques
- Droits stale : les droits sont modifiés par un admin mais le cache
/men'est pas invalidé. Ajouter un mécanisme de polling léger ou une notification WebSocket. appRightsvide mais user actif : l'utilisateur est actif mais n'a aucun profil assigné. Contacter l'admin du Customer.adminLevelmal interprété : le niveau est une string, pas un entier — comparer par valeur exacte ou par inclusion dans un tableau ordonné.
Aller plus loin
- Contrôle d'accès : comment utiliser
appRightsdans vos guards - Utilisation de l'API : comment passer les tokens dans vos requêtes