You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🐛 [BUG] - jwt expired récurrent : le client n'actualise pas le token avant les requêtes API #2591
Le serveur rejette périodiquement des requêtes authentifiées avec l'erreur jwt expired, alors que la session utilisateur est toujours active côté client.
Le client (apps/client/src/api/xhr-client.ts) attache keycloak.token sans appeler keycloak.updateToken() au préalable.
Le rafraîchissement ne s'effectue que via le timer passif onTokenExpired et un contournement dans onAuthSuccess : toute requête émise à proximité de l'expiration — ou après une période d'onglet inactif (timers throttlés par le navigateur) — transporte un JWT déjà expiré.
Le gestionnaire 401 (extractData) ne redirige vers le login que si !keycloak.authenticated ; il ne tente jamais de rafraîchir le token.
Etapes de reproduction
Ouvrir la console et laisser l'onglet en arrière-plan jusqu'à l'expiration de l'access token Keycloak
Revenir sur l'onglet et déclencher une requête API
Le serveur répond 401 avec jwt expired
Captures d'écran
Logs
UnauthorizedException: jwt expired
Navigateurs
Tous
OS
Tous
Version de la console impactée
apps/client (xhr-client) avec apps/server-nestjs (KeycloakJwtService)
Contrat : chaque requête API transporte un access token Keycloak valide. Observé : le serveur (keycloak-jwt.service.ts, verifyAsync) rejette avec jwt expired.
Cause racine — apps/client/src/api/xhr-client.ts:24 : le fetcher lit keycloak.token directement, sans attendre un rafraîchissement. Le rafraîchissement repose uniquement sur :
le timer passif onTokenExpired (utils/keycloak/keycloak.ts:40) ;
un contournement setTimeout dans onAuthSuccess (keycloak.ts:35).
Les deux sont asynchrones et sujets aux courses : onglet en arrière-plan (throttling des timers), requête émise près de l'expiration. De plus, extractData (401) ne déclenche login() que si !keycloak.authenticated — une session authentifiée mais au token expiré échoue en boucle.
Solution proposée
Un seul changement au point de passage unique (le fetcher) :
constkeycloak=getKeycloak()awaitkeycloak.updateToken(30)// no-op si validité > 30 s ; rafraîchit sinonif(keycloak.token){args.headers.Authorization=`Bearer ${keycloak.token}`}
Test de régression : vérifier que le fetcher attend updateToken avant de lire .token quand la validité est sous le seuil.
Activity
shikanime commentedon Aug 26, 2026
Enquête — cause racine
Contrat : chaque requête API transporte un access token Keycloak valide.
Observé : le serveur (
keycloak-jwt.service.ts,verifyAsync) rejette avecjwt expired.Cause racine —
apps/client/src/api/xhr-client.ts:24: le fetcher litkeycloak.tokendirectement, sans attendre un rafraîchissement. Le rafraîchissement repose uniquement sur :onTokenExpired(utils/keycloak/keycloak.ts:40) ;setTimeoutdansonAuthSuccess(keycloak.ts:35).Les deux sont asynchrones et sujets aux courses : onglet en arrière-plan (throttling des timers), requête émise près de l'expiration. De plus,
extractData(401) ne déclenchelogin()que si!keycloak.authenticated— une session authentifiée mais au token expiré échoue en boucle.Solution proposée
Un seul changement au point de passage unique (le fetcher) :
Test de régression : vérifier que le fetcher attend
updateTokenavant de lire.tokenquand la validité est sous le seuil.