← Retour au blog

Rotation des jetons d'actualisation JWT : modèle sécurisé pour les systèmes réels

Sécurité des développeurs28 mars 2026·9 minutes de lecture
JWT refresh token rotation diagram

La plupart des didacticiels JWT s'arrêtent à "utiliser un jeton d'actualisation pour les sessions de longue durée". Ils expliquent rarement ce qui se passe lorsque ce jeton d'actualisation est volé. Un jeton d'actualisation statique, qui ne change jamais, donne à un attaquant le même accès persistant qu'un cookie de session volé. Pire encore, il n'y a aucun moyen de détecter le vol.

Refresh token rotation résout ce problème en émettant un nouveau jeton d'actualisation à chaque utilisation. Si un ancien jeton est rejoué, la session entière est révoquée. Cet article présente le modèle de mise en œuvre utilisé par Auth0, Okta et d'autres fournisseurs d'identité sérieux.

Pourquoi les jetons d'actualisation statique échouent

Un jeton d'actualisation statique a une valeur fixe pour toute la durée de vie de la session (souvent plus de 30 jours). Problèmes :

Le correctif consiste à traiter chaque jeton d'actualisation comme à usage unique.

Le modèle de rotation

Étape 1 : Connexion – Créer une famille de jetons

Lorsque l'utilisateur s'authentifie, générez un family_id (UUID) qui regroupe tous les jetons de cette session :

{ family_id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", user_id: "user_123", refresh_token_hash: sha256(refresh_token), created_at: "2026-03-28T10:00:00Z", expires_at: "2026-04-27T10:00:00Z", used: false
}

Stockez cet enregistrement côté serveur. Ne stockez jamais de jetons d'actualisation dans localStorage : utilisez les cookies httpOnly ou un stockage natif sécurisé.

Étape 2 : Actualisation du jeton — Rotation

Lorsque le client présente le jeton d'actualisation :

  1. Recherchez le hachage du jeton dans la base de données
  2. Vérifiez qu'il appartient à une famille active et qu'il n'est pas marqué comme used
  3. Marquez-le comme utilisé : true
  4. Émettre un new jeton d'actualisation (nouvelle valeur aléatoire, même family_id)
  5. Émettre un nouveau jeton d'accès
  6. Stocker le nouveau hachage du jeton d'actualisation

Étape 3 : Détection de relecture

Si un jeton d'actualisation marqué comme utilisé : true est à nouveau présenté, cela signifie soit :

  • Un attaquant rejoue un jeton volé, ou
  • Un client légitime réessaye après une panne de réseau
  • La réponse sûre : révoquer tous les jetons de la famille. Cela oblige l'utilisateur à se reconnecter. Il est préférable de gêner un utilisateur légitime une fois que de laisser un attaquant conserver l'accès.

    // Replay détecté – option nucléaire
    attendre db.query( 'DELETE FROM actualiser_tokens OÙ family_id = $1', [IDfamille]
    );

    Schéma de base de données

    Une table de jetons d'actualisation minimale :

    CREATE TABLE rafraîchir_tokens ( id UUID CLÉ PRIMAIRE PAR DÉFAUT gen_random_uuid(), family_id UUID NON NULL, user_id UUID NON NULL RÉFÉRENCES utilisateurs(id), token_hash TEXTE NON NULL UNIQUE, utilisé BOOLEAN DEFAULT FALSE, créé_à TIMESTAMPTZ DEFAULT maintenant(), expires_at TIMESTAMPTZ NON NULL, remplacé_par RÉFÉRENCES UUID rafraîchir_tokens(id)
    ); CRÉER UN INDEX idx_refresh_family ON rafraîchir_tokens(family_id);
    CRÉER UN INDEX idx_refresh_user ON rafraîchir_tokens(user_id);

    La colonne replaced_by crée une chaîne que vous pouvez suivre à des fins d'audit.

    Gestion des demandes simultanées

    Dans les applications réelles, plusieurs appels d'API peuvent déclencher une actualisation simultanément. Si l'appel A fait tourner le jeton alors que l'appel B détient toujours l'ancien, l'appel B déclenche la détection de relecture, déconnectant ainsi l'utilisateur de manière inattendue.

    Modèle de période de grâce

    Autoriser le jeton d'actualisation précédent à rester valide pendant une courte fenêtre (5 à 10 secondes) après la rotation :

    const GRACE_PERIOD_MS = 10_000 ; si (jeton.utilisé) { const écoulé = Date.now() - token.rotated_at; si (écoulé < GRACE_PERIOD_MS) { // Within grace period — return the already-issued new tokens return getLatestTokensForFamily(token.family_id); } // Outside grace period — replay attack await revokeFamily(token.family_id); throw new UnauthorizedError('token_replayed');
    }

    Surveillance et alertes

    Suivez ces signaux en production :

    Jeton d'actualisation et jeton d'accès : référence rapide

    PropriétéJeton d'accèsActualiser le jeton
    Durée de vie5 à 15 minutes7 à 30 jours
    FormatJWT (autonome)Chaîne opaque (recommandée)
    StockageMémoire (variable JS)httpCookie uniquement
    Envoyé àServeurs APIServeur d'authentification uniquement
    RotationNon nécessaireChaque utilisation

    Pour les bogues de validation de revendication de temps qui interagissent souvent avec les flux d'actualisation, voir JWT exp/iat/nbf bugs courants.

    FAQ

    Comment dois-je gérer les demandes d'actualisation simultanées ?

    Utilisez une courte période de grâce (5 à 10 secondes) pendant laquelle le jeton d'actualisation précédent est toujours accepté. Cela gère les conditions de concurrence lorsque plusieurs appels d’API déclenchent une actualisation simultanément. Après la période de grâce, seul le jeton le plus récent est valide.

    Les jetons d'actualisation doivent-ils être des chaînes JWT ou opaques ?

    Les chaînes opaques sont généralement plus sûres pour les jetons d'actualisation. Les JWT transportent des données utiles qui augmentent la surface d'attaque, et les jetons d'actualisation n'ont pas besoin d'être autonomes puisque le serveur les recherche toujours dans la base de données.

    Combien de temps les sessions d'actualisation doivent-elles durer ?

    7 à 30 jours est typique. Les applications de haute sécurité (banque, soins de santé) devraient prendre 1 à 7 jours. Les applications grand public peuvent durer jusqu'à 90 jours avec rotation. Associez toujours les longues sessions à la prise d'empreintes digitales de l'appareil et à la détection d'anomalies.

    Outils et articles connexes