Sécurité API Zero-Trust : JWTs Courts, Rotation de Refresh Tokens et Révocation
Sécuriser l’authentification stateless contre le vol de tokens et les attaques par rejeu.
1. Le danger des tokens d’accès à longue durée de vie
Un JWT avec une expiration de 24 heures, voire 7 jours, est un bearer token : celui qui détient la chaîne de caractères est traité comme l’utilisateur, point final. Il n’y a par défaut aucune liaison à un appareil, une session ou une plage d’IP, donc dès qu’un token fuite - via une charge XSS, un pipeline de logs mal configuré, une extension de navigateur qui lit le localStorage, ou un proxy qui journalise les en-têtes - il reste valide jusqu’à l’expiration inscrite dans son claim exp. L’attaquant n’a pas besoin de votre mot de passe, il n’a plus besoin de retoucher votre serveur d’auth, il rejoue simplement le token jusqu’à expiration.
L’instinct de corriger ce problème en allongeant la durée de vie des tokens pour une meilleure expérience utilisateur va exactement à l’inverse de ce qu’il faut faire. Chaque heure de durée de vie supplémentaire est une heure de rayon d’impact supplémentaire pour chaque token qui fuite. Le bon levier est l’inverse : réduire la durée de vie du token d’accès au point où un token volé devient quasiment inutile seul, et déplacer la continuité réelle de session vers un mécanisme séparé, révocable et surveillable.
C’est le mouvement central d’une posture zero-trust appliquée à l’authentification par token : ne jamais faire confiance à un identifiant simplement parce qu’il était valide il y a une minute. Chaque requête redérive la confiance à partir d’un token suffisamment court pour que la question a-t-il déjà été volé devienne une fenêtre bien plus réduite, typiquement 5 à 15 minutes pour les clients web ou mobile.
2. Séparer token d’accès et refresh token
Le schéma standard repose sur une paire : un JWT d’accès court (5 à 15 minutes), envoyé à chaque appel API, et un refresh token long (jours à semaines), utilisé uniquement pour émettre de nouveaux tokens d’accès. Le token d’accès est vérifié par simple contrôle de signature et ne touche jamais la base de données - c’est tout l’intérêt d’un JWT ici : rapide et scalable horizontalement. Le refresh token, à l’inverse, est recherché dans un store à chaque usage, car il doit être révocable et sa réutilisation doit être détectable.
Cette séparation compte parce qu’elle permet d’optimiser deux choses différentes indépendamment. Le token d’accès optimise la vitesse de vérification et l’absence d’état sur le chemin critique (chaque appel API). Le refresh token optimise le contrôle - vous voulez savoir précisément quels refresh tokens existent, quand ils ont été utilisés pour la dernière fois, depuis quel appareil, et pouvoir en tuer un instantanément sans toucher aux autres sessions.
Une erreur fréquente consiste à ne placer les données d’autorisation sensibles (rôles, permissions, tenant ID) que dans le token d’accès, en traitant le refresh token comme une chaîne opaque et bête. C’est correct pour le contenu du refresh token, mais cela signifie que tout changement de privilège (rétrogradation de rôle, suspension de compte) ne prend effet qu’à l’expiration du token d’accès en cours. Avec 15 minutes de durée de vie, c’est acceptable ; avec 24 heures, ça ne l’est pas.

