WakaStart
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 /me et les patterns d'invalidation. Lisez Structure du payload /me d'abord.


Quand appeler /me

SituationAppeler /me ?
Au login (après enrichissement)Oui — stocker dans le context
Après un refresh tokenOui — les droits peuvent avoir changé
À chaque navigationNon — 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'organisationOui

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 :

  1. Stocker dans un React context (en mémoire, pas en localStorage).
  2. Invalider le cache après chaque refresh token (les droits peuvent avoir changé).
  3. 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 :

typescript
const { 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 /me dans localStorage — il contient des droits sensibles.
  • Utilisez les appRights uniquement pour l'affichage conditionnel côté front. Le backend est la source d'autorité.
  • Si adminLevel === "None" et appRights.length === 0, l'utilisateur n'a aucun droit — affichez une page dédiée.
  • Vérifiez isActive au chargement : si false, redirigez vers une page "Compte désactivé".
  • Utilisez features pour 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 /me n'est pas invalidé. Ajouter un mécanisme de polling léger ou une notification WebSocket.
  • appRights vide mais user actif : l'utilisateur est actif mais n'a aucun profil assigné. Contacter l'admin du Customer.
  • adminLevel mal interprété : le niveau est une string, pas un entier — comparer par valeur exacte ou par inclusion dans un tableau ordonné.

Aller plus loin