appuyez sur ⌘K pour changer d’outil
SECURITY

Décodeur de Tokens JWT

Décode les JSON Web Tokens et visualise leur contenu.

local
jwt-decoder

Decoding only — the signature is not verified. Nothing is sent anywhere.

§01 À PROPOS DE CET OUTIL

Vue d’ensemble

Un JWT est composé de trois parties encodées en base64url et séparées par des points : un en-tête indiquant comment il a été signé, une charge utile de revendications, et une signature portant sur les deux premières. L’encodage n’est pas du chiffrement. Quiconque détient le jeton peut lire chacune de ses revendications, ce qu’il vaut mieux avoir intériorisé avant de placer quoi que ce soit dans une charge utile.

Cet outil décode les deux premières parties et les met en forme, puis extrait les trois revendications temporelles et les convertit de secondes Unix en horodatages lisibles. Il ne vérifie pas la signature — voir plus bas pourquoi c’est le bon choix et non une fonctionnalité manquante.

Utilisation

  1. Collez le jeton. Tout ce qui suit Bearer — le schéma lui-même ne fait pas partie du JWT.
  2. Lisez l’en-tête pour voir l’algorithme et, s’il est présent, l’identifiant de clé.
  3. Lisez la charge utile pour les revendications.
  4. Consultez le tableau des horodatages pour iat, nbf, exp et le verdict d’expiration.

Les revendications enregistrées

RevendicationNomRemarques
issÉmetteurQui a émis le jeton. Votre serveur devrait le comparer à une liste d’émetteurs autorisés
subSujetDe qui parle le jeton. En général un identifiant utilisateur stable, pas une adresse e-mail
audAudiencePour qui le jeton a été émis. Un jeton émis pour une autre audience doit être rejeté
expExpirationSecondes Unix. Passé cet instant, rejeter
nbfPas avantSecondes Unix. Avant cet instant, rejeter
iatÉmis àSecondes Unix. Utile pour « réauthentifier si plus ancien que N »
jtiIdentifiant du JWTIdentifiant unique, pour révoquer un jeton ou contrôler les rejeux

Les sept sont optionnelles dans la spécification. Un jeton dépourvu d’exp n’expire jamais de lui-même, ce qui est un choix de conception à remarquer quand on le rencontre.

Pourquoi la vérification de signature n’est pas proposée

Vérifier une signature exige la clé : le secret partagé pour HS256, ou la clé publique pour RS256 et ES256. Dans le cas symétrique, la clé qui vérifie est aussi celle qui signe — une page proposant la vérification vous demanderait donc de coller l’identifiant qui permet à n’importe qui d’émettre des jetons pour votre système. Il n’existe aucune version de cela qu’il soit sûr de construire.

Le décodage vaut malgré tout la peine. L’essentiel du débogage de JWT consiste à répondre à « que dit vraiment ce jeton » — mauvaise audience, portée manquante, un exp cinq minutes dans le passé, un sub qui est une adresse e-mail alors que votre code attend un UUID. Tout cela est visible sans la clé.

Ce que le décodage ne peut pas vous dire, c’est si le jeton est authentique. Un JWT que vous recevez est une affirmation, pas un fait, jusqu’à ce que votre serveur vérifie la signature avec une clé en laquelle il a confiance.

Le champ alg n’est pas une décision que votre vérificateur doit déléguer

Le champ alg de l’en-tête décrit ce que l’expéditeur a utilisé. Un vérificateur qui lit alg et choisit son algorithme en conséquence fait confiance à une entrée contrôlée par l’attaquant, ce qui produit deux ruptures classiques :

  • alg: none. Le JWT non sécurisé est légal dans la spécification. Un vérificateur qui l’honore accepte n’importe quelle charge utile accompagnée d’une signature vide.
  • RS256 échangé contre HS256. Si une bibliothèque prend la clé publique et l’utilise comme secret HMAC, un attaquant qui connaît votre clé publique — elle est publique — peut signer des jetons que vous accepterez.

Les deux sont des problèmes de configuration, pas de cryptographie. Fixez l’algorithme attendu dans votre vérificateur et rejetez tout le reste, au lieu de le lire dans le jeton.

Exemples

  • Un 401 que vous n’expliquez pas. Décodez et vérifiez aud. Un jeton émis pour une API et présenté à une autre est la cause la plus fréquente, et le message d’erreur le dit rarement.
  • Un 401 intermittent. Comparez exp à iat. Une durée de vie courte plus un client qui met le jeton en cache est intermittent par construction.
  • Une permission qui devrait fonctionner. Cherchez scope ou roles dans la charge utile. Si la revendication est absente, le problème est chez l’émetteur, pas dans votre code d’autorisation.
  • Auditer ce que vous mettez dans un jeton. Décodez l’un des vôtres. Tout ce qui s’y trouve — adresse e-mail, identifiants internes, drapeaux de fonctionnalité — est lisible par quiconque détient le jeton, y compris par le navigateur qui le stocke.

Où un jeton doit vivre dans un navigateur

La question revient chaque fois que quelqu’un décode un jeton et constate qu’il est lisible. En résumé : puisqu’un JWT est un identifiant porteur, tout ce qui peut le lire peut l’utiliser.

localStorage est lisible par n’importe quel script de l’origine, ce qui signifie qu’une XSS emporte le jeton avec elle. Il n’est jamais envoyé automatiquement, en revanche, donc on ne peut pas vous attaquer en CSRF par ce biais — vous échangez une classe de bugs contre une autre.

Un cookie HttpOnly ne peut pas être lu par un script du tout, donc une XSS ne peut pas le voler. Il est envoyé automatiquement, il lui faut donc SameSite=Lax ou Strict en plus de Secure, et tout point d’entrée qui modifie un état a besoin de sa propre défense contre le CSRF.

La conclusion habituelle pour une application de navigateur est un jeton d’accès à durée de vie courte conservé uniquement en mémoire, renouvelé depuis un cookie de jeton de rafraîchissement HttpOnly, Secure, SameSite. Ainsi une XSS obtient un jeton qui vit quelques minutes plutôt qu’un identifiant valable des semaines, et le secret à longue durée de vie n’est jamais atteignable depuis JavaScript.

Quel que soit votre choix, c’est exp qui fait le vrai travail. Un jeton d’une heure de durée de vie sans liste de révocation reste valable une heure après son vol, et rien du côté client n’y change quoi que ce soit.

Remarques

Le remplissage est ajouté avant le décodage, parce que le base64url l’omet normalement. Les jetons copiés depuis des journaux arrivent parfois avec leur remplissage intact, et les deux formes fonctionnent ici.

Le verdict d’expiration utilise votre horloge locale : c’est donc une commodité plutôt qu’une autorité. Si vous devez comparer à un instant précis, utilisez le convertisseur d’horodatage Unix sur la valeur brute d’exp.

Les charges utiles ne sont pas toujours du JSON en principe — la spécification autorise n’importe quel type de contenu — mais tous les JWT rencontrés en pratique transportent un objet JSON, et une charge utile qui n’arrive pas à s’analyser comme du JSON est affichée en texte brut plutôt que masquée.

FAQ
Cet outil vérifie-t-il la signature ?
Non, et c'est délibéré. Vérifier exige la clé de signature, et coller une clé de signature dans une page web est précisément l'erreur que cet outil ne doit pas encourager. Le décodage vous dit ce qu'un jeton affirme ; seul votre serveur peut vous dire si cette affirmation est vraie.
Mon jeton est-il envoyé quelque part ?
Non. Le jeton est découpé sur les points et décodé en base64url dans la page. Il n'y a aucune requête réseau. Considérez malgré tout comme grillé tout jeton que vous collez quelque part — un jeton porteur passé par l'historique de votre presse-papiers ou par une session de navigateur est un identifiant que vous ne maîtrisez plus totalement.
Pourquoi affiche-t-il EXPIRED alors que mon API accepte encore le jeton ?
Le statut compare exp à l'horloge de votre machine. Si votre horloge est décalée, ou si l'émetteur tolère une dérive d'horloge (quelques minutes, c'est courant), les deux verdicts peuvent diverger. La valeur exp du jeton est le nombre qui fait autorité ; l'étiquette n'est qu'une commodité.
Puis-je décoder un jeton qui n'a que deux parties ?
Oui. Deux parties, c'est soit un JWT non sécurisé valide (alg: none), soit un jeton collé sans sa signature. L'outil a besoin de l'en-tête et de la charge utile ; la signature n'est pas utilisée.
La charge utile ressemble à du charabia. Qu'est-ce qui a mal tourné ?
Le plus souvent, le jeton est chiffré et non signé (un JWE, qui compte cinq parties), ou bien quelque chose l'a réencodé — une copie depuis un terminal qui a replié la ligne, ou depuis une chaîne JSON qui l'a échappé. Une charge utile JWS est du base64url pur et se décode toujours en JSON.