Comment décoder et inspecter des jetons JWT
Les JSON Web Tokens (JWT) sont la manière la plus courante de gérer l'authentification dans les applications web modernes. Quand quelque chose va de travers avec l'auth, un utilisateur est déconnecté de manière inattendue, les permissions sont fausses, une API renvoie 401, décoder le JWT est généralement la première étape de débogage. Comprendre les trois parties d'un JWT, les revendications standard, les algorithmes avec lesquels il peut être signé, et les pièges courants transforme le débogage d'auth de vaudou en vérification de routine.
Une brève histoire de JWT
Les JWT ont été standardisés dans la RFC 7519 en mai 2015, après plusieurs années d'itération en brouillon à l'IETF. Le format a emprunté aux conceptions de jetons compacts précédentes (assertions SAML, simples cookies opaques) mais a ajouté deux choses qui leur manquaient : une forme JSON stricte lisible dans n'importe quel langage, et un encodage base64url-safe qui survivait aux paramètres d'URL, en-têtes HTTP et champs de formulaire sans nouvel échappement. Les spécifications complémentaires, JWS (RFC 7515) pour les signatures, JWE (RFC 7516) pour le chiffrement, et JWA (RFC 7518) pour les noms d'algorithme, forment ensemble la famille JOSE (JavaScript Object Signing and Encryption).
OAuth 2.0 et OpenID Connect ont adopté JWT comme format de jeton par défaut peu après, c'est pourquoi presque tous les fournisseurs d'auth modernes (Auth0, Okta, Cognito, Keycloak, Firebase, Supabase, Clerk) en émettent aujourd'hui. La combinaison de jetons autonomes et de back-ends sans état s'est avérée s'adapter très naturellement aux microservices et passerelles d'API. L'inconvénient est que les JWT sont notoirement faciles à mal utiliser, et la dernière décennie a produit un flux régulier de CVE dans des bibliothèques qui n'ont pas validé l'algorithme avec soin.
Ce qu'il y a dans un JWT
Un JWT a trois parties, séparées par des points :
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
En-tête : contient l'algorithme (HS256, RS256, etc.) et le type de jeton.
{"alg": "HS256", "typ": "JWT"}
Charge utile : contient les revendications (assertions de données) sur l'utilisateur et le jeton.
{"sub": "1234567890", "name": "Alice", "exp": 1700000000}
Signature, un hash cryptographique qui vérifie que le jeton n'a pas été altéré. Vous ne pouvez pas la lire sans la clé de signature.
Chaque section est encodée en base64url, ce qui signifie qu'elle utilise - et _ au lieu de + et / et omet le bourrage = final. Base64url n'est pas du chiffrement ; coller juste le segment central dans n'importe quel décodeur révèle la charge utile. C'est par conception : les segments centraux sont conçus pour être lisibles par les services en chemin, la signature est la seule partie qui prouve l'authenticité.
Revendications JWT courantes
Les revendications standard sont enregistrées auprès de l'IANA et définies dans la RFC 7519. La plupart sont optionnelles, mais celles ci-dessous sont presque toujours présentes.
| Revendication | Nom complet | Ce qu'elle contient |
|---|---|---|
sub | Subject (sujet) | ID ou identifiant utilisateur |
exp | Expiration | Horodatage Unix quand le jeton expire |
iat | Issued At (émis le) | Horodatage Unix de création du jeton |
iss | Issuer (émetteur) | Qui a créé le jeton (votre serveur d'auth) |
aud | Audience | À qui le jeton est destiné |
nbf | Not Before (pas avant) | Le jeton n'est pas valide avant ce moment |
jti | JWT ID | Identifiant unique du jeton |
azp | Authorized Party | La partie à qui le jeton a été émis (OIDC) |
scope / scp | Portées OAuth | Permissions accordées, souvent séparées par espace |
email | Identifiant utilisateur OIDC standard | |
name | Nom | Nom d'affichage (OIDC) |
nonce | Nonce | Valeur OIDC de protection contre rejeu |
kid (en-tête) | Key ID | Quelle clé de signature a été utilisée (pour lookup JWKS) |
Au-delà du jeu standard, les applications ajoutent leurs propres revendications personnalisées (roles, tenant_id, feature_flags, permissions). Les noms de revendications personnalisés ne sont pas namespaces par défaut, ce qui signifie que deux services différents peuvent utiliser le même nom pour des choses différentes ; la convention OIDC de préfixer avec une URI (https://myapp.com/roles) évite la collision.
Comment décoder un JWT
- Collez votre jeton : entrez le JWT complet (format header.payload.signature) dans le décodeur. Les décodeurs basés navigateur le traitent localement, le jeton ne quitte jamais la page.
- Voir les sections décodées : l'outil affiche l'en-tête (algorithme), la charge utile (revendications), et la signature en JSON formaté, avec les horodatages affichés à la fois en entiers Unix et en dates lisibles.
- Vérifier les revendications : examinez le temps d'expiration, l'émetteur, le sujet, l'audience, et toute revendication personnalisée qui pilote votre logique d'autorisation.
- Comparer aux attentes : croisez l'émetteur avec le fournisseur d'auth que vous avez configuré, l'audience avec l'API à laquelle le jeton est envoyé, et toute revendication de rôle/portée avec les permissions que l'utilisateur devrait avoir.
- Test de voyage dans le temps : survolez
iat,nbf, etexppour voir si le jeton est actuellement valide, va bientôt expirer, ou a été émis il y a si longtemps que votre tolérance de dérive d'horloge ne le couvre plus.
Algorithmes de signature
Tous les JWT n'utilisent pas la même crypto. L'en-tête alg vous dit à quelle famille appartient la signature, et chacune a des propriétés de sécurité très différentes.
| Algorithme | Famille | Type de clé | Quand choisir |
|---|---|---|---|
HS256 | HMAC | Secret partagé | Apps mono-service ; ne partagez jamais le secret entre équipes |
HS384 / HS512 | HMAC | Secret partagé | Comme HS256 avec des digests plus longs |
RS256 | RSA | Paire de clés publique/privée | Le plus courant pour OIDC ; les vérificateurs n'ont besoin que de la clé publique |
RS384 / RS512 | RSA | Paire de clés | Comme RS256 avec clés plus grandes |
PS256 / PS384 / PS512 | RSA-PSS | Paire de clés | RSA moderne, recommandé plutôt que RS pour nouveaux déploiements |
ES256 / ES384 / ES512 | ECDSA | Paire de clés à courbe elliptique | Clés plus petites que RSA, vérification plus rapide |
EdDSA | Ed25519 | Paire de clés courbe d'Edwards | Le plus récent, le plus petit, le plus rapide ; pas encore universel |
none | Aucun | Aucune | Interdit en production ; certaines vieilles bibliothèques l'acceptent encore |
Les algorithmes asymétriques (RS*, PS*, ES*, EdDSA) permettent à tout service de vérifier un jeton avec juste une clé publique, ce qui est pourquoi ils dominent OIDC. Symétrique (HS*) est correct dans une seule application mais devient un cauchemar à faire tourner ou distribuer entre plusieurs consommateurs.
Déboguer avec les JWT
Jeton expiré ? Vérifiez la revendication exp. Convertissez l'horodatage Unix en date lisible. S'il est dans le passé, le jeton a expiré et doit être rafraîchi. La plupart des bibliothèques JWT rejettent les jetons expirés par défaut ; si votre app les accepte, c'est un bug de sécurité.
Mauvaises permissions ? Cherchez les revendications de rôle ou portée dans la charge utile. Elles varient par implémentation mais ressemblent souvent à "role": "admin" ou "scope": "read write profile".
Problèmes d'identité utilisateur ? La revendication sub identifie l'utilisateur. Vérifiez qu'elle correspond à l'ID utilisateur attendu. Notez que certains fournisseurs utilisent des GUID opaques tandis que d'autres utilisent des adresses e-mail ; le décodeur vous montre exactement ce qui est là.
Jeton non accepté ? Vérifiez la revendication aud (audience). Si l'API attend une valeur d'audience spécifique et que le jeton en a une différente, il sera rejeté. Les décalages d'audience sont un symptôme courant de l'acheminement d'un jeton vers le mauvais service.
Erreurs 401 après déploiement ? Vérifiez la revendication iss (émetteur). Un nouveau tenant de fournisseur d'auth ou une clé de signature changée modifie l'URL de l'émetteur ; si votre vérificateur fait toujours confiance à l'ancien, tous les jetons paraissent invalides.
Problèmes de dérive d'horloge ? Si iat est légèrement dans le futur ou exp légèrement dans le passé, l'horloge de votre serveur peut dériver. La plupart des bibliothèques JWT autorisent quelques secondes de latitude ; sinon, une horloge synchronisée NTP corrige le problème.
Pièges courants
- Faire confiance à l'en-tête
algsans liste blanche, la vulnérabilité JWT classique était un serveur acceptant l'algorithme que le jeton disait utiliser. Un jeton avecalg: none(sans signature) oualg: HS256(signé avec votre clé publique comme secret) pouvait forger n'importe quelle charge utile. Épinglez le vérificateur à l'algorithme exact attendu. - Mettre des secrets dans la charge utile, la charge utile est encodée en base64url, pas chiffrée. Quiconque a le jeton peut la lire. N'incluez jamais mots de passe, clés API, ou quoi que ce soit que vous ne mettriez pas dans une chaîne de requête.
- Jetons longue durée sans révocation, un JWT de 30 jours ne peut pas être révoqué sans liste noire de jetons ou stockage de session. Gardez les jetons d'accès courts (5-60 minutes) et utilisez un flux de jeton de rafraîchissement pour les sessions plus longues.
- Oublier la dérive d'horloge, les serveurs dans des fuseaux horaires différents ou avec horloges dérivées rejettent les jetons qui devraient être valides. Autorisez 30-60 secondes de latitude sur
expetnbf. - Faire confiance à l'émetteur sans vérification, la revendication
issfait partie de la charge utile que n'importe qui peut écrire. Vérifier que sa valeur correspond à l'émetteur configuré est requis ; le mettre en liste blanche empêche un attaquant de frapper des jetons qui prétendent venir de votre fournisseur. - Réutiliser les secrets HS256 entre environnements, le même secret en dev et prod signifie qu'un jeton de dev fuité fonctionne en production. Utilisez des clés par environnement, idéalement récupérées depuis un gestionnaire de secrets.
- Stocker les jetons dans localStorage, localStorage est lisible par tout JavaScript, donc un seul bug XSS exfiltre tous les jetons des utilisateurs. Les cookies HttpOnly avec SameSite=Lax (ou Strict) sont le défaut plus sûr.
- Journaliser les jetons complets, les logs d'application qui capturent des JWT complets fuient les jetons à quiconque a accès aux logs. Tronquez aux 10 premiers caractères ou ne journalisez que le
jti. - Ignorer la rotation
kid, quand un fournisseur fait tourner les clés de signature, les nouveaux jetons ont un nouvel en-têtekid. Un vérificateur qui met en cache le JWKS pour toujours commencera à rejeter des jetons valides. Re-récupérez le JWKS en cas de manque de key-id. - Mélanger JWT avec sessions de manière incohérente, certains endpoints derrière JWT, d'autres derrière sessions cookies, mène à des bugs où un utilisateur connecté apparaît non authentifié sur une route. Choisissez un modèle par service.
Alternatives à JWT
JWT est dominant mais pas la seule option. Chaque alternative échange des propriétés différentes.
| Mécanisme | Force | Faiblesse |
|---|---|---|
| JWT (JWS) | Autonome, facile entre services | Ne peut être révoqué sans état supplémentaire |
| Jetons opaques + introspection | Facile à révoquer, cache les revendications | Chaque requête touche le serveur d'auth |
| Sessions côté serveur | Modèle le plus simple, révocation instantanée | Difficile à mettre à l'échelle entre services |
| PASETO | Remplacement plus sûr de JWT (pas de confusion alg) | Écosystème plus petit |
| Macaroons | Atténuation intégrée (droits délégués) | Support de bibliothèque limité |
| OAuth 2.0 + jetons d'accès JWT | Standard de l'industrie pour les API | Spec large, facile à mal implémenter |
| Jetons ID OIDC | Identité utilisateur standard + JWT | Souvent confondu avec jetons d'accès |
| Certificats clients mTLS | Auth la plus forte à la couche transport | Surcoût de gestion des certificats |
Pour la plupart des équipes, le choix est entre JWT et jetons opaques. JWT gagne quand la vérification doit être pas chère et hors ligne ; les jetons opaques gagnent quand la révocation doit être instantanée.
Vie privée et le décodeur
Le décodeur JWT tourne entièrement dans votre navigateur. Le jeton que vous collez est divisé, décodé en base64url, et le JSON est parsé et joliment imprimé sans aucune requête réseau. Il n'y a aucun journal des jetons qui ont été décodés, aucune analytique sur les revendications qu'ils contiennent, et aucun moyen pour quiconque de reconstruire pour qui vous étiez en train de déboguer. Les JWT contiennent souvent des identifiants utilisateur, adresses e-mail, noms de rôles internes, et IDs de tenant, exactement le genre de métadonnées que vous ne voulez pas envoyer au serveur d'un inconnu. Décoder côté client garde cette information sur votre machine, ce qui est le bon défaut pour toute tâche de débogage qui touche à l'authentification.
Questions fréquentes
Puis-je vérifier une signature JWT avec un décodeur ?
Non. La vérification de signature nécessite le secret de signature ou la clé publique, conservés sur votre serveur. Un décodeur vous montre ce qu'il y a dans le jeton, mais la vérification cryptographique doit se faire sur votre backend. Ne faites jamais confiance à un JWT non vérifié en production.
Est-il sûr de coller un JWT dans un outil en ligne ?
Oui, quand l'outil tourne dans votre navigateur. Les décodeurs dans le navigateur traitent le jeton localement, rien n'est envoyé sur un serveur. Évitez les outils qui font des requêtes réseau avec votre jeton.
Qu'est-ce que la revendication exp ?
La revendication exp (expiration) est un horodatage Unix indiquant quand le jeton expire. Après cette date, le jeton doit être rejeté. Vérifiez toujours cette revendication pour déboguer des problèmes d'authentification.
Les JWT peuvent-ils être chiffrés ?
Les JWT standards (JWS) sont signés mais pas chiffrés, n'importe qui peut décoder la charge utile. Les jetons JWE (JSON Web Encryption) sont chiffrés, mais moins courants. Ne mettez jamais de données sensibles (mots de passe, secrets) dans une charge utile JWT standard.
What is the alg none vulnerability?
Early JWT libraries accepted tokens with an alg header set to "none", meaning the signature could be omitted entirely. An attacker who set this header could forge any payload. Modern libraries reject "none" by default, but legacy systems may still be exposed; always allow-list the expected algorithm rather than trusting the header.
How should I store a JWT on the client?
HttpOnly secure cookies with SameSite=Lax (or Strict) are the safest default; they cannot be read by JavaScript, which mitigates XSS token theft. localStorage is convenient but vulnerable to any XSS bug. Never store long-lived JWTs alongside untrusted scripts.