Skip to content

🐛 [BUG] - Avertissement Keycloak : refreshToken expire avant le token #2593

Open

Description

@shikanime

Description

Le client journalise l'avertissement Keycloak misconfiguration: refreshToken should not expire before token (apps/client/src/utils/keycloak/keycloak.ts:34).

Ce contrôle dans keycloak.onAuthSuccess détecte une configuration où le refresh token expire avant ou en même temps que le access token. Observé sur le realm dso : ssoSessionMaxLifespan (10 h) plafonne l'exp du refresh token, donc après un refresh réussi le nouveau access token (5 min) peut expirer après le refresh token — la condition s'inverse et produit un faux positif, alors même que la configuration du realm est correcte.

Le garde-fou est pertinent uniquement à l'authentification initiale ; lors des refreshs il signale à tort.

Etapes de reproduction

  1. Se connecter à la console
  2. Laisser la session approcher de ssoSessionMaxLifespan (10 h) avec des refreshs périodiques
  3. Observer l'avertissement Keycloak misconfiguration: refreshToken should not expire before token dans les logs navigateur

Captures d'écran

Logs

Object { time: 1787730753864, level: 40, msg: "Keycloak misconfiguration: refreshToken should not expire before token" }

Navigateurs

OS

Version de la console impactée

Définition du fini

  • La cause racine est identifiée : faux positif du contrôle client après refresh, exp du refresh token plafonnée par ssoSessionMaxLifespan
  • Le contrôle ne se déclenche plus à tort lors des refreshs (réservé au cas d'authentification initiale)
  • L'avertissement n'apparaît plus lors d'une session standard

Activity

  1. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Investigation

    Le contrôle se trouve dans onAuthSuccess (apps/client/src/utils/keycloak/keycloak.ts:31) :

    if (!(keycloak.refreshTokenParsed?.exp && keycloak.tokenParsed?.exp && keycloak.refreshTokenParsed.exp > keycloak.tokenParsed.exp)) {
      return
    }
    logger.warn('Keycloak misconfiguration: refreshToken should not expire before token')

    Deux observations :

    1. La condition d'échappement est une négation stricte : si refreshTokenParsed.exp <= tokenParsed.exp, le client logue l'avertissement mais ne programme aucun rafraîchissement préventif — l'utilisateur subira une déconnexion à l'expiration du access token.
    2. Le realm de développement (keycloak/dev-realm) définit probablement des durées par défaut (SSO Session Idle 1h, access token 5 min) qui devraient rendre le refresh plus long que l'access ; la présence de l'avertissement suggère un realm réel avec Refresh Token Max Reuse ou des SSO Session settings atypiques.

    Pistes :

    • Vérifier la configuration effective du realm (Admin CLI : get realms/<realm> — champs accessTokenLifespan, ssoSessionIdleTimeout, revokeRefreshToken).
    • Décider si le correctif est purement opérationnel (config realm) ou si le client doit dégrader proprement (forcer re-auth au lieu de warning silencieux).
  2. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Enquête — diagnostic

    Le garde-fou (keycloak.ts:31-38) compare refreshTokenParsed.exp et tokenParsed.exp à l'authentification. L'avertissement ne peut se déclencher que si le realm émet un refresh token dont la vie ≤ celle de l'access token : c'est une configuration realm, pas un bug client.

    Preuve (realm dev du dépôt, keycloak/realms/realm-dev.json) :

    • accessTokenLifespan: 300
    • ssoSessionIdleTimeout: 600 → le refresh token vit 600 s > 300 s : l'avertissement ne se produit pas en dev.
    • revokeRefreshToken / refreshTokenMaxReuse non définis (défauts sains).

    Cause racine attendue : le realm de l'environnement où le log apparaît a soit SSO Session Idle ≤ Access Token Lifespan, soit Revoke Refresh Token activé avec Refresh Token Max Reuse trop bas — le refresh échoue alors au premier renouvellement et la session meurt avec le premier access token.

    Solution proposée (côté configuration, aucune ligne de code) :

    1. Sur le realm fautif, relever SSO Session Idle Timeout strictement au-dessus d'Access Token Lifespan.
    2. Si Revoke Refresh Token est activé, mettre Refresh Token Max Reuse ≥ nombre de renouvellements attendus (ou désactiver la révocation).
    3. Vérifier après reconnexion que l'avertissement disparaît (critère « Définition du fini » n°1 et n°3).

    Le contournement côté client (setTimeout de keycloak.ts:35) reste nécessaire tant que des realms mal configurés existent ; il devient supprimable une fois le realm corrigé.

    Note croisée : ce défaut de realm explique probablement la récurrence observée dans #2591 — mais #2591 reste valable indépendamment (course sur onTokenExpired, onglet en arrière-plan).

  3. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Cause racine identifiée : condition inversée côté client

    Le garde-fou de apps/client/src/utils/keycloak/keycloak.ts:30-39 est inversé :

    if (!(keycloak.refreshTokenParsed?.exp && keycloak.tokenParsed?.exp && keycloak.refreshTokenParsed.exp > keycloak.tokenParsed.exp)) {
      return
    }
    logger.warn('Keycloak misconfiguration: refreshToken should not expire before token')
    const refreshTokenDelay = (keycloak.tokenParsed.exp * 1000 - Date.now()) / 2
    setTimeout(() => keycloak.updateToken(), refreshTokenDelay)

    Deux effets, tous deux à contre-sens :

    1. Avertissement en configuration saine : le warn se déclenche quand refreshToken.exp > token.exp, c'est-à-dire exactement le cas normal (realm dev du dépôt : SSO idle 600 s > access 300 s). D'où le log à chaque connexion standard, sans aucune faute de configuration realm.
    2. Contournement appliqué au mauvais cas : le timer de renouvellement à mi-vie (workaround prévu pour un refresh token trop court) ne s'exécute que dans le cas sain ; le cas réellement dysfonctionnel sort par return anticipé.

    Correctif proposé : inverser la condition — avertir et armer le timer uniquement quand refreshToken.exp <= token.exp. Aucun changement de configuration Keycloak nécessaire (retractation de l'hypothèse config du commentaire précédent).

  4. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Convergence : configuration du realm observée

    Relevé en direct sur le cluster (dso-keycloak, realm dso, via kcadm) :

    Paramètre Valeur
    accessTokenLifespan 300 s (5 min)
    ssoSessionIdleTimeout 1800 s (30 min)
    ssoSessionMaxLifespan 36 000 s (10 h)
    revokeRefreshToken false
    refreshTokenMaxReuse 0

    Le realm dso n'a donc pas de refresh token intrinsèquement plus court que le access token : à l'émission initiale, l'avertissement ne devrait pas se déclencher.

    Analyse

    Sous Keycloak, l'exp du refresh token est borné par la session SSO (idle et surtout max lifespan, ici 10 h). Le contrôle client compare refreshTokenParsed.exp > tokenParsed.exp à chaque onAuthSuccess :

    • après un refresh réussi, le nouveau access token repart à now + 300 s, tandis que l'exp du refresh token reste plafonnée par la fin de session SSO ;
    • quand la session approche de son ssoSessionMaxLifespan (10 h), l'écart se réduit jusqu'à inverser la condition → l'avertissement apparaît alors même que la configuration est correcte.

    Le même rapport existe dans le realm de dev du dépôt (keycloak/realms/realm-dev.json : idle 600 s / max 10 h / access 300 s).

    Pistes de correctif

    1. Côté client (keycloak.ts) : traiter ce cas comme attendu en fin de session (ex. ne warn que si refreshTokenParsed.exp <= tokenParsed.exp dès l'authentification initiale, ou passer le message en info/debug).
    2. Côté opérationnel : augmenter ssoSessionMaxLifespan si des sessions de plus de 10 h sont souhaitées.

    La piste 1 semble la bonne : le garde-fou actuel détecte une vraie mauvaise configuration uniquement à l'authentification initiale ; lors des refreshs, il produit un faux positif.

  5. shikanime commented on Aug 26, 2026

    @shikanime
    MemberAuthor

    Proposition de correctif (client)

    Aucune PR existante ne couvre ce cas (les plus proches — #1339, #2301 — traitent d'autres sujets). Le garde-fou doit ne s'exécuter qu'à l'authentification initiale :

        let firstAuth = true
        keycloak.onAuthSuccess = () => {
          if (!firstAuth) return
          firstAuth = false
          if (!(keycloak.refreshTokenParsed?.exp && keycloak.tokenParsed?.exp && keycloak.refreshTokenParsed.exp > keycloak.tokenParsed.exp)) {
            return
          }
    

    Ainsi les refreshs successifs (où l'exp du refresh token plafonnée par ssoSessionMaxLifespan inverse l'inégalité) ne déclenchent plus le faux positif ; une vraie mauvaise configuration reste détectée au premier login.

  6. self-assigned this
    on Aug 26, 2026
  7. modified the milestones: 9.24.5, 9.25.0 on Aug 26, 2026
  8. modified the milestones: 9.25.0, 9.26.0 on Sep 1, 2026
  9. modified the milestones: 9.26.0, 9.27.0 on Sep 14, 2026
  10. moved this from Backlog to Ready in Cloud Pi Nativeon Sep 29, 2026
  11. added theissue type on Sep 29, 2026
  12. 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