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

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 vol est silencieux — si un attaquant vole le jeton d'actualisation, il peut générer de nouveaux jetons d'accès indéfiniment sans que l'utilisateur le sache
- Aucune détection de relecture — le même jeton peut être utilisé à plusieurs reprises à partir de différents endroits
- La révocation est grossière — pour invalider un jeton volé, vous devez souvent invalider toutes les sessions utilisateur
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 :
- Recherchez le hachage du jeton dans la base de données
- Vérifiez qu'il appartient à une famille active et qu'il n'est pas marqué comme
used - Marquez-le comme
utilisé : true - Émettre un new jeton d'actualisation (nouvelle valeur aléatoire, même
family_id) - Émettre un nouveau jeton d'accès
- 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 :
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 :
- Rejouer les événements par heure — un pic indique une attaque active ou un bug côté client
- Révocations familiales — des taux élevés peuvent indiquer un vol de jeton ou un client mal configuré
- Taux de rafraîchissement par utilisateur — des taux anormalement élevés suggèrent une récolte de jetons
- Anomalies géographiques — actualisation à partir d'un pays différent de celui de la connexion d'origine
Jeton d'actualisation et jeton d'accès : référence rapide
| Propriété | Jeton d'accès | Actualiser le jeton |
|---|---|---|
| Durée de vie | 5 à 15 minutes | 7 à 30 jours |
| Format | JWT (autonome) | Chaîne opaque (recommandée) |
| Stockage | Mémoire (variable JS) | httpCookie uniquement |
| Envoyé à | Serveurs API | Serveur d'authentification uniquement |
| Rotation | Non nécessaire | Chaque 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
- JWT Decoder — inspecter les jetons et vérifier les réclamations de temps
- Erreurs de sécurité JWT — pièges JWT courants au-delà des jetons d'actualisation
- JWT Time-Claim Bugs — problèmes de décalage d'horloge et d'ordre de validation
- 2FA Guide — renforcer l'authentification au-delà des jetons