Skip to content

🐛 [BUG] - jwt expired récurrent : le client n'actualise pas le token avant les requêtes API #2591

Description

@shikanime

Description

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

  1. Ouvrir la console et laisser l'onglet en arrière-plan jusqu'à l'expiration de l'access token Keycloak
  2. Revenir sur l'onglet et déclencher une requête API
  3. 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)

Définition du fini

  • Le correctif est terminé
  • Les tests liés à ce correctif ont été ajoutés

Activity

  1. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Enquête — cause racine

    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) :

    const keycloak = getKeycloak()
    await keycloak.updateToken(30) // no-op si validité > 30 s ; rafraîchit sinon
    if (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.

  2. self-assigned this
    on Aug 26, 2026
  3. modified the milestones: , 9.25.0 on Aug 26, 2026
  4. modified the milestones: 9.25.0, 9.26.0 on Sep 1, 2026
  5. modified the milestones: 9.26.0, 9.27.0 on Sep 14, 2026
  6. added theissue type on Sep 29, 2026
  7. modified the milestones: 9.27.0, 9.28.0, 9.29.0 on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions