Les JSON Web Tokens sont populaires parce qu'une API peut en vérifier un sans appeler personne : vérifier la signature, lire les claims, c'est fini. Cette propriété est aussi la source de la plupart des problèmes de JWT. Un jeton que vous ne pouvez pas consulter est un jeton que vous ne pouvez pas rappeler, et les équipes le découvrent la première fois qu'un portable est volé ou qu'un employé s'en va.
Cet article déroule un flux d'authentification JWT complet avec des jetons d'accès, des jetons d'actualisation (refresh tokens) et un diagramme de séquence, puis couvre les contrôles dont chaque API a besoin, l'endroit où garder les jetons dans un navigateur, la rotation des refresh tokens, la rotation des clés et les options réalistes de révocation.
Ce qu'est un JWT, et ce qu'il n'est pas
Un JWT (RFC 7519) est une chaîne compacte, sûre pour les URL, composée de trois parties base64url séparées par des points : un en-tête, un payload de claims et une signature. Décodé, un jeton d'accès ressemble à ceci :
{ "alg": "RS256", "kid": "2026-10", "typ": "at+jwt" }
{
"iss": "https://auth.example.com",
"sub": "user_8412",
"aud": "orders-api",
"scope": "orders:read orders:write",
"iat": 1793606400,
"exp": 1793607300,
"jti": "a3f1c9e2"
}
Trois points causent la plupart des malentendus :
- Un JWT signé n'est pas chiffré. Toute personne qui détient le jeton peut lire le payload. N'y mettez pas de secrets ni de données personnelles que le porteur ne devrait pas voir.
- JWT est un format, pas un protocole. La façon dont un client obtient un jeton est définie ailleurs, en général par OAuth 2.0 et OpenID Connect. Le diagramme de flux OAuth 2.0 couvre cette partie.
- Tous les jetons d'un flux ne sont pas des JWT. Les jetons d'accès le sont souvent ; les refresh tokens sont souvent des chaînes aléatoires opaques que seul le serveur d'autorisation peut consulter.
Les claims enregistrés que vous rencontrerez dans chaque jeton :
| Claim | Signification | Qui le vérifie |
|---|---|---|
iss |
Issuer, le serveur d'autorisation qui a signé le jeton | API |
sub |
Subject, en général l'identifiant de l'utilisateur | API, pour l'autorisation |
aud |
Audience, l'API à laquelle le jeton est destiné | API |
exp, nbf, iat |
Expiration, début de validité et heure d'émission, en secondes depuis l'epoch | API |
jti |
Identifiant unique du jeton, utile pour les listes de refus | API ou passerelle |
Le flux, étape par étape
- Connexion. L'utilisateur s'authentifie auprès du serveur d'autorisation, en général via le flux OAuth authorization code avec PKCE.
- Réponse de jetons. Le serveur d'autorisation renvoie un jeton d'accès de courte durée (un JWT, disons 15 minutes) et un refresh token de longue durée.
- Appel d'API. L'application envoie
Authorization: Bearer <access token>avec chaque requête. - Validation locale. L'API vérifie la signature avec la clé publique du serveur d'autorisation, récupérée une fois depuis le point de terminaison JWKS et mise en cache, puis contrôle les claims. Aucun appel au serveur d'autorisation par requête.
- Expiration. Au bout de 15 minutes, l'API répond
401 Unauthorized. - Actualisation. L'application envoie le refresh token au point de terminaison de jetons du serveur d'autorisation et obtient un nouveau jeton d'accès et, avec la rotation, un nouveau refresh token.
- Nouvel essai. L'application rejoue la requête échouée avec le nouveau jeton d'accès.
Les étapes 3 et 4 sont la raison pour laquelle les équipes choisissent les JWT : des milliers de requêtes d'API et aucun stockage de session partagé. Les étapes 5 et 6 en sont le prix, et c'est là que se concentre l'essentiel du travail de sécurité.
Valider un jeton : la liste de contrôles
Toute API qui accepte des JWT devrait exécuter les mêmes contrôles, dans cet ordre :
- N'autorisez que les algorithmes attendus. Ne prenez jamais l'en-tête
algpour argent comptant. La RFC 8725 décrit les deux attaques classiques :algréglé surnone, et un jeton RS256 réécrit en HS256 pour qu'une bibliothèque naïve le vérifie en utilisant la clé publique comme secret HMAC. - Choisissez la clé par
kiddans le JWKS en cache, et rechargez le jeu quand vous voyez unkidinconnu, au plus une fois par court intervalle. - Vérifiez la signature.
- Vérifiez
issetaud. Un jeton émis pour une autre API doit échouer ici. - Vérifiez
expetnbfavec une petite tolérance d'horloge, par exemple 60 secondes. - Vérifiez le type de jeton. Les jetons d'accès de la RFC 9068 portent
typ: at+jwt, ce qui empêche de rejouer un ID token comme jeton d'accès. - Puis autorisez : scopes, rôles et propriété de la ressource.
Avec la bibliothèque jose en Node.js, l'essentiel de la liste tient en un appel :
import { createRemoteJWKSet, jwtVerify } from 'jose'
const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'))
export async function verifyAccessToken(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://auth.example.com',
audience: 'orders-api',
algorithms: ['RS256'],
typ: 'at+jwt',
clockTolerance: 60,
})
return payload // scopes and sub are checked by the caller
}
Combien de temps un jeton d'accès doit-il vivre ?
La durée de vie est la fenêtre pendant laquelle un jeton volé fonctionne sans pouvoir être rappelé. Plus court signifie plus sûr, et plus de trafic d'actualisation. Pour un utilisateur actif pendant une journée de travail de huit heures :
| Durée de vie du jeton d'accès | Exposition en cas de vol | Actualisations par utilisateur et par jour |
|---|---|---|
| 5 minutes | 5 minutes | 96 |
| 15 minutes | 15 minutes | 32 |
| 1 heure | 1 heure | 8 |
| 24 heures | 24 heures | 1 |
La plupart des équipes retiennent entre 5 et 15 minutes pour les jetons d'accès. Un jeton d'accès valable une journée est un cookie de session que vous ne pouvez pas révoquer.
La rotation des refresh tokens
Un refresh token vit des jours ou des semaines ; en divulguer un est donc pire que divulguer un jeton d'accès. La RFC 9700, la bonne pratique actuelle de sécurité OAuth, impose l'une de deux protections pour les refresh tokens détenus par des clients publics comme les applications navigateur et mobiles :
- Des refresh tokens liés à l'émetteur, rattachés à une clé que détient le client, via mutual TLS ou DPoP.
- La rotation : chaque actualisation renvoie un nouveau refresh token et invalide l'ancien, tandis que le serveur d'autorisation garde la trace de la chaîne. Si un attaquant et le vrai client utilisent le même jeton, l'un d'eux présente un jeton invalidé, et le serveur révoque le refresh token actif, ce qui met fin à la chaîne pour les deux.
La rotation transforme un refresh token volé, problème de plusieurs semaines, en un problème qui se termine à la prochaine actualisation du client légitime. Le prix : l'utilisateur doit se reconnecter, et votre point de terminaison de jetons doit gérer deux onglets qui actualisent en même temps sans déclencher la détection de réutilisation, en général avec un court délai de grâce.
Où garder les jetons dans un navigateur
| Stockage | Lisible par JavaScript | Risque principal | Remarques |
|---|---|---|---|
localStorage |
Oui | Toute faille XSS peut voler les jetons | Survit aux rechargements ; à éviter pour les refresh tokens |
| Mémoire uniquement | Oui, tant que la page vit | XSS pendant la session | Perdu au rechargement ; actualiser au démarrage |
Cookie HttpOnly |
Non | CSRF | Utilisez Secure, SameSite, et des jetons CSRF pour les requêtes qui modifient l'état |
| Backend for frontend | Non ; les jetons restent sur le serveur | Gestion des sessions côté serveur | Le navigateur ne détient qu'un cookie de session |
Le brouillon de l'IETF sur OAuth pour les applications basées sur le navigateur décrit le pattern backend for frontend comme l'option qui tient entièrement les jetons à l'écart de JavaScript. Si vous gardez les jetons dans le navigateur, gardez le jeton d'accès en mémoire et le refresh token dans un cookie HttpOnly limité au point de terminaison de jetons.
La révocation : ce qui fonctionne vraiment
On ne peut pas « dé-signer » un JWT. On peut seulement faire en sorte que l'API cesse de l'accepter, et chaque option rend une part de l'absence d'état :
| Stratégie | Fonctionnement | Coût |
|---|---|---|
| Expiration courte | Les jetons meurent d'eux-mêmes en quelques minutes | Aucun ; la fenêtre d'exposition demeure |
| Révoquer le refresh token | La prochaine actualisation échoue, donc l'accès prend fin à la prochaine expiration | Une recherche au point de terminaison de jetons, qui a déjà un état |
Liste de refus par jti |
Les passerelles rejettent les identifiants listés jusqu'à leur exp |
Un cache partagé, consulté à chaque requête |
| Date butoir par utilisateur | Rejeter les jetons émis avant un horodatage stocké pour cet utilisateur | Une recherche par utilisateur, mise en cache possible |
| Introspection | L'API interroge le serveur d'autorisation sur chaque jeton (RFC 7662) | Un appel réseau ; à réserver aux points de terminaison à haut risque |
Une combinaison pragmatique : des jetons d'accès de 15 minutes ou moins, des refresh tokens rotatifs révoqués à la déconnexion et au changement de mot de passe, et une date butoir par utilisateur pour le rare cas « déconnecter partout immédiatement ».
Faire tourner les clés de signature
Les clés fuient et les algorithmes vieillissent ; planifiez donc la rotation dès le premier jour. Publiez la nouvelle clé publique dans le JWKS environ un jour avant de signer avec elle, pour que chaque cache d'API l'ait. Basculez la signature sur le nouveau kid. Gardez l'ancienne clé dans le JWKS jusqu'à l'expiration du dernier jeton signé avec elle, puis retirez-la. Une API qui recharge le jeu sur un kid inconnu survit à une rotation d'urgence sans redémarrage.
Le diagramme du flux JWT
Contenu du diagramme
- Browser app
- Auth server
- Orders API
- access token expired
- 1. Sign in (OAuth code flow + PKCE)
- 2. Access token (JWT, 15 min) + refresh token
- 3. GET /orders, Authorization: Bearer JWT
- GET /.well-known/jwks.json (once, then cached)
- public keys
- 4. 200 OK
- 5. GET /orders (expired JWT)
- 6. 401 Unauthorized
- 7. POST /token (refresh token)
- 8. New token pair, old refresh token invalidated
- 9. Retry GET /orders with new JWT
- 10. 200 OK
Comment le lire :
- Trois lignes de vie. L'application navigateur à gauche, le serveur d'autorisation au milieu et l'API des commandes à droite. Le temps s'écoule vers le bas.
- La connexion et la réponse de jetons en haut : l'application obtient un jeton d'accès et un refresh token.
- Le chemin nominal : la requête bearer vers l'API et la réponse. L'API valide le jeton avec les clés JWKS en cache et n'appelle jamais le serveur d'autorisation.
- Le fragment opt couvre l'expiration :
401, actualisation, nouvelle paire de jetons avec l'ancien refresh token invalidé, et la requête rejouée. - Le bloc Markdown montre le jeton décodé et les contrôles de validation.
Dessiner des flux d'authentification vérifiables
- Nommez chaque jeton sur chaque flèche, avec son type et sa durée de vie : « jeton d'accès (JWT, 15 min) ». L'ambiguïté sur quel jeton circule où cache de vrais bugs.
- Montrez où la validation a lieu. Placez une note sur la ligne de vie qui vérifie la signature, et indiquez d'où viennent ses clés.
- Dessinez les chemins d'échec. Expiration, réutilisation de refresh token et déconnexion ont leur place dans des fragments ; la plupart des incidents commencent là.
- Gardez les secrets hors de l'image. Utilisez des valeurs fictives ; les diagrammes circulent plus loin qu'on ne le pense.
L'exemple de diagramme de séquence détaille les lignes de vie et les fragments, et le diagramme d'architecture zero trust montre où se placent les contrôles de jetons dans un modèle d'accès plus large.
Le construire dans Archboard
- Connectez-vous avec Google ou GitHub et créez un diagramme.
- Ajoutez trois nœuds Ligne de vie du pack Séquence : Browser app, Auth server et Orders API.
- Dessinez les messages comme des arêtes entre les lignes de vie, de haut en bas : connexion, réponse de jetons, requête bearer et sa réponse. Étiquetez chacun avec le jeton qu'il transporte.
- Ajoutez un Fragment combiné du pack Séquence avec l'opérateur
optautour du chemin d'expiration :401, demande d'actualisation, nouvelle paire de jetons, nouvel essai. - Ajoutez un bloc Markdown du pack Base avec le jeton décodé et la liste de contrôles.
L'exemple utilise 5 nœuds de diagramme et reste dans les limites de l'offre Free.
FAQ
Les JWT sont-ils chiffrés ? Les JWT signés ne le sont pas. Le payload est encodé en base64url et lisible par quiconque possède le jeton. JSON Web Encryption existe pour les jetons chiffrés, mais la plupart des jetons d'accès sont seulement signés.
Faut-il stocker un JWT dans localStorage ? Évitez-le pour tout ce qui dure longtemps. N'importe quelle faille de cross-site scripting peut lire localStorage. Gardez les jetons d'accès en mémoire et les refresh tokens dans un cookie HttpOnly, ou gardez les jetons sur un backend for frontend.
Comment déconnecter un utilisateur si les JWT sont sans état ? Révoquez le refresh token pour qu'aucun nouveau jeton d'accès ne soit émis, et gardez des jetons d'accès courts. Pour un effet immédiat, ajoutez une date butoir par utilisateur ou une liste de refus par jti que votre API vérifie.
Quelle est la différence entre un ID token et un jeton d'accès ? Un ID token indique au client qui s'est connecté et lui est destiné. Un jeton d'accès est destiné à une API. Les API doivent rejeter les ID tokens ; les contrôles de typ et d'aud le rendent explicite.
Faut-il utiliser HS256 ou RS256 ? HS256 utilise un secret partagé unique : tout service qui vérifie des jetons peut donc aussi en fabriquer. RS256 ou ES256 permet aux API de vérifier avec une clé publique, tandis que seul le serveur d'autorisation détient la clé privée ; c'est le choix par défaut le plus sûr dès qu'il y a plus d'un service.