Accueil / Articles

Explication des jetons de renouvellement et des délais d’expiration des sessions dans Keycloak

Pourquoi la mise à jour de Keycloak renvoie 400, en quoi les délais de expiration Idle et Max diffèrent, et comment gérer les tokens de mise à jour avec un stockage sécurisé contre la rotation en JavaScript.

Éditeur : REACTAPP.TOP7 min de lecture1636 mots

Sources: Refresh Tokens and Session Timeouts in Keycloak: Why a 400 Isn’t Always a Bug

Les applications basées sur Keycloak voient parfois l’endpoint des tokens répondre 400 Bad Request lors du renouvellement. En bref, cela signifie que le processus de renouvellement ne fonctionne pas. Plus fréquemment, Keycloak refuse délibérément : la session est devenue inactive, a atteint son âge maximal, a été révoquée, ou le token de renouvellement n’est plus valide. Ces situations relèvent des politiques définies, et non de défauts techniques.

Ce guide explique les éléments essentiels en environnement de production : la fonction des tokens d’accès et de renouvellement, le déroulement complet du processus de renouvellement, les deux compteurs de temps des sessions qui conditionnent un renouvellement réussi, ainsi que les modèles et anti-modèles à adopter pour gérer le reste.

Pourquoi cet article existe

Lorsque l’endpoint des tokens renvoie 400, les équipes enregistrent souvent un bug d’authentification. La première question utile à se poser est de savoir quelle règle liée à la durée de vie ou à la révocation a été activée. Comprendre cette réponse évite de considérer un refus correct comme une panne.

1. Deux tokens, deux fonctions très différentes

Lors de la connexion, Keycloak délivre généralement un token d’accès, un token de renouvellement et un token d’ID. Le fonctionnement quotidien repose sur les deux premiers.

  • Token d’accès — Attaché aux appels API ordinaires pour prouver l’identité de l’utilisateur. Sa durée de vie est courte, souvent mesurée en minutes.
  • Token de renouvellement — Envoyé uniquement à Keycloak afin d’obtenir un nouveau token d’accès. Sa durée de vie est plus longue, généralement trente minutes ou plus.
  • Token hors ligne — Accès temporaire optionnel dont la durée de vie peut dépasser celle d’une session SSO normale, géré par une politique distincte.

Exemple concret : le token d’accès est une carte temporaire présentée à chaque porte. Le token de renouvellement correspond aux documents qui remplacent cette carte ; il n’est jamais montré aux API des applications et doit être envoyé uniquement à l’endpoint de tokens de Keycloak.

2. Le flux normal

Le connexion restitue l’ensemble des jetons. Par la suite, le client ajoute le jeton d’accès à chaque appel :

GET /api/products
Authorization: Bearer eyJ...

La durée de validité des accès est courte — cinq à quinze minutes étant courants. Si la connexion a lieu à 09:00 avec un jeton d’accès valable cinq minutes, dès 09:05 le prochain appel API renvoie 401 Unauthorized.

Ce code 401 ne signifie pas un déconnexion immédiate ; il s’agit d’un signal indiquant qu’il faut renouveler le jeton.

3. L’appel de renouvellement lui-même

Le client envoie les données au point de terminaison des jetons de Keycloak en utilisant le jeton de renouvellement :

POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
client_id=<client-id>
refresh_token=<refresh-token>

Un état correct se manifeste comme suit :

{
  "access_token": "new-access-token",
  "expires_in": 300,
  "refresh_token": "new-refresh-token",
  "refresh_expires_in": 1800
}

Le client stocke le nouveau jeton d’accès, réessaie la demande initiale, et l’utilisateur ne perçoit jamais ce changement.

4. Détails de la rotation : remplacer à chaque fois les identifiants de renouvellement stockés

La réponse inclut souvent également un jeton de mise à jour nouveau. Lorsque la rotation des jetons de mise à jour est activée, l’ancien jeton devient invalide dès que le nouveau est émis. La réutilisation de la valeur ancienne entraîne un échec lors du renouvellement suivant.

// Correct
saveAccessToken(response.access_token);
if (response.refresh_token) {
  saveRefreshToken(response.refresh_token);
}
// Wrong — keeps using the token that rotation just invalidated
const refreshToken = originalRefreshToken;

C’est le fait de conserver la valeur de mise à jour mise à jour après chaque succès – et non seulement après connexion – qui distingue une session qui se renouvelle silencieusement pendant des heures d’une autre qui échoue dès la deuxième tentative.

5. Inactif versus Max : les compteurs qui contrôlent le renouvellement

Les realms disposent d’au moins deux compteurs de session, et le changement de leur signification est à l’origine de la plupart des demandes d’aide du type « pourquoi l’authentification a échoué » :

  • Inactivité de la session SSO – Durée pendant laquelle la session peut rester sans activité avant que Keycloak ne la termine. Un renouvellement réussi compte comme une activité et réinitialise ce compteur.
  • Durée maximale de la session SSO — Un plafond fixe pour l’âge total mesuré depuis le premier connexion. Le renouvellement ne le réinitialise pas. Rien ne le fait.
  • En bref :

    IDLE = how long since the session was last active
    MAX  = how long since the user originally logged in
    

    Avec Idle = 30 minutes et Max = 16 heures, des renouvellements fréquents maintiennent le compteur d’inactivité à un niveau bas toute la journée, mais après seize heures depuis la connexion, la session se termine même si l’utilisateur était actif une minute plus tôt. Un timing de renouvellement parfait ne peut pas dépasser cette durée maximale ; il ne fait que prolonger la vie de la session dans cette fenêtre temporelle.

    Les paramètres Idle/Max par client reflètent les compteurs du domaine, mais uniquement pour ce client. La limite la plus stricte s’applique.

    Pourquoi une durée maximale de seize heures apparaît dans les exemples : une équipe ERP travaillant sur des shifts d’environ douze heures peut choisir douze heures de travail accompagnées d’une marge de quatre heures afin que la session puisse durer toute une journée sans nécessiter un nouveau connexion en cours de tâche, tout en évitant qu’elle ne dure indéfiniment. Ce chiffre est une décision commerciale ; la compétence essentielle consiste à savoir quel mécanisme il régule.

    Pourquoi un temps d’inactivité de trente minutes surprend encore les gens : même avec une durée maximale généreuse, un déjeuner ou une réunion longue peut provoquer l’inactivité prématurément. Si les utilisateurs doivent revenir sans devoir se réauthentifier, il convient d’envisager l’inactivité comme une décision liée au produit ou à la sécurité — et non simplement comme un problème à masquer dans le code client.

    6. Lire 400 depuis l’endpoint du token sans supposer une panne

    Une mise à jour échouée peut se manifester comme suit :

    {
      "error": "invalid_grant",
      "error_description": "Token is not active"
    }
    

    Ce payload peut correspondre à plusieurs cas distincts :

    • Les identifiants de mise à jour sont devenus obsolètes d’eux-mêmes
  • La politique d’inactivité a fermé la session SSO
  • L’âge maximal depuis le connexion a été atteint
  • Aucun de ces cas ne correspond à « l’API est down ». Consultez les champs error et error_description avant d’en déduire autre chose.

    7. Gestion correcte au niveau frontend

    Un schéma fiable :

    401 on API request
      → try refresh, exactly once
          → success: save new tokens, retry the original request
          → failure: clear auth state, redirect to login
    

    Ne continuez pas indéfiniment les tentatives : 401, renouvellement, 400, renouvellement à nouveau. Une session défectueuse peut alors provoquer une avalanche de requêtes liées aux jetons et un indicateur d’affichage en cours qui ne s’arrête jamais. Considérez l’échec du renouvellement comme définitif : supprimez les identifiants, effacez l’état d’authentification, redirigez vers la page de connexion et arrêtez le processus.

    8. Les erreurs 401 en cascade nécessitent une seule renouvellement partagé

    Un tableau de bord qui envoie cinq requêtes simultanément avec un jeton d’accès expiré reçoit cinq erreurs 401. Si chaque chemin est renouvelé de manière indépendante selon un mécanisme de rotation, le premier succès invalide le jeton que les quatre autres possèdent encore — ce qui entraîne quatre échecs de renouvellement avec 400 invalid_grant, même si la session était valide.

    Sérialisez le renouvellement : la première erreur 401 déclenche une seule promesse ; les autres requêtes attendent cette même promesse et tentent à nouveau après qu’elle ait été résolue.

    if (tokenExpired) {
      if (!refreshInProgress) {
        refreshInProgress = refreshToken();
      }
    
      try {
        const tokens = await refreshInProgress;
        updateTokens(tokens);
        return retryOriginalRequest();
      } finally {
        refreshInProgress = null;
      }
    }
    

    Que le mécanisme de verrouillage soit une promesse gérée manuellement, une file d’intercepteurs Axios ou un enveloppe baseQuery de RTK Query, la règle reste inchangée : un seul renouvellement en cours, quel que soit le nombre d’appels en attente.

    9. Paramètres qui semblent liés mais ne le sont pas : Délai de connexion et Délai d’action de connexion

    Deux autres paramètres de realm peuvent facilement être confondus avec la durée de vie d’une session, mais ils ne régissent que la procédure de connexion :

    • Délai d’expiration de la connexion — Durée pendant laquelle l’ensemble du processus d’authentification (identifiants, MFA, actions requises) peut rester ouvert.
    • Délai d’expiration de l’action de connexion — Durée pendant laquelle une étape spécifique (par exemple l’écran du code OTP) peut rester sans réponse.

    Aucun de ces paramètres ne prolonge une session déjà authentifiée. Ils n’ont d’importance que tant que l’utilisateur est encore en train de se connecter.

    10. Sessions hors ligne — un outil différent pour un problème différent

    Les realms peuvent également générer des sessions hors ligne via scope=offline_access. Celles-ci permettent l’émission de nouveaux tokens d’accès même après la fin de la session SSO normale — destinées à permettre de rester connecté « demain et la semaine prochaine », et non pour une seule journée de travail.

    Pour une utilisation normale en heures de travail (une période de service plus une marge de sécurité), l’accès hors ligne est généralement inutile. Une valeur ajustée pour Session Max permet une gestion plus simple et plus efficace. N’optez pour l’accès hors ligne que lorsque la persistance sur plusieurs jours ou semaines est réellement nécessaire.

    11. Liste de contrôle pour le débogage : lorsque la mise à jour renvoie 400

    Suivez cet ordre :

    1. Lisez les valeurs error et error_description dans la réponse.
    2. Comparez l’heure de connexion avec la valeur Session Max au niveau du domaine et du client.
    3. Comparez le temps d’inactivité avec la valeur Session Idle au niveau du domaine et du client.
    4. Vérifiez les paramètres personnalisés du client sous Clients → <client> → Advanced.
    5. Vérifiez si la rotation des tokens de mise à jour est activée.
    6. Vérifiez s’il y a eu plusieurs demandes de mise à jour simultanées.
    7. Assurez-vous que le client enregistre le dernier token de mise à jour après chaque renouvellement, et non seulement le premier.
  • Vérifier si la session a été terminée manuellement par l’utilisateur ou un administrateur.
  • 12. Le modèle mental à retenir

    Trois niveaux, avec une dépendance unidirectionnelle :

    Access token                   → used for normal API calls
    Refresh token                   → used to get a new access token
    Keycloak session (Idle + Max)   → decides whether that refresh is allowed at all
    

    Liste de contrôle

    • Traiter l’expiration du jeton d’accès séparément de la fin de la session — tenter un seul renouvellement avant d’interpréter une erreur 401 comme une déconnexion.
    • Conserver le jeton de renouvellement mis à jour après chaque succès, et non seulement le nouveau JWT d’accès.
    • Se souvenir des deux compteurs temporels : l’un renouvelle la session en cas d’inactivité ; l’autre, basé sur l’heure réelle depuis la connexion, détermine l’âge maximal.
    • Faire en sorte que l’échec du renouvellement soit définitif — une seule tentative, puis effacer les identifiants et rediriger l’utilisateur vers la page de connexion ; ne jamais réessayer en boucle.
  • Fusionner les erreurs 401 simultanées en une seule promesse de renouvellement partagée au lieu de cinq appels indépendants.
  • Garder le délai de timeout de connexion et le délai de timeout d’action de connexion dans le groupe relatif à la procédure de connexion ; ils ne font pas partie des catégories Idle/Max.
  • Réserver les sessions hors ligne aux besoins de connexion maintenue sur plusieurs jours, et non à un SSO standard d’une durée de shift.
  • Lire en premier le contenu d’erreur de l’endpoint du token ; un code 400 correspond généralement à un refus spécifique de politique, et non à une panne.