Pour décoder un jeton JWT, divisez la chaîne en trois parties à l'aide de ses deux points, puis décodez l'en-tête et la charge utile en base64url. La charge utile est un simple JSON que tout le monde peut lire, car un JWT est encodé, pas chiffré. En JavaScript, vous pouvez le faire en une ligne avec atob, mais seulement après avoir corrigé les caractères base64url qui cassent un décodage naïf. Ce guide montre exactement comment décoder un jeton JWT à la main, l'écueil que la plupart des extraits oublient, et pourquoi le décodage ne vous dit rien sur l'authenticité du jeton.
Si vous avez déjà collé un jeton quelque part et vu les revendications apparaître instantanément, c'est la partie qui déroute les gens. Aucune clé secrète n'est impliquée dans la lecture d'un JWT. La confidentialité vient de la signature, pas du masquage du contenu, et confondre ces deux idées est ainsi que les développeurs introduisent de véritables bugs. Décodons-en d'abord un correctement, puis clarifions le malentendu dangereux qui se cache derrière.
À quoi ressemble réellement un JWT#
Un JSON Web Token est une chaîne unique composée de trois parties reliées par des points :
header.payload.signature
Un vrai jeton ressemble à ceci (coupé ici pour la lisibilité, il s'agit d'une chaîne continue) :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNzE3NzI4MDAwLCJleHAiOjE3MTc3MzE2MDB9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Chaque partie a un rôle :
- En-tête (Header) : un petit objet JSON qui nomme l'algorithme de signature (
alg) et le type de jeton (typ). Encodé en base64url. - Charge utile (Payload) : l'objet JSON contenant vos revendications (identifiant utilisateur, rôles, expiration, tout ce que vous y mettez). Encodé en base64url.
- Signature : un hachage cryptographique de l'en-tête et de la charge utile, calculé avec une clé secrète ou privée. C'est la seule partie qui prouve que le jeton n'a pas été modifié.
L'en-tête et la charge utile ne sont pas cachés. Ils sont en base64url, ce qui est réversible par n'importe qui sans clé. Traitez tout ce qui se trouve dans la charge utile d'un JWT comme une information publique.
Pourquoi base64url et pas du base64 standard#
Les JWT se retrouvent dans les URL, les en-têtes HTTP et les cookies. Le base64 standard utilise les caractères +, / et le remplissage =, qui sont tous dangereux ou gênants dans ces contextes. Les JWT utilisent donc le base64url, défini dans la RFC 4648, qui remplace + par -, / par _ et supprime complètement le remplissage =.
Cette seule différence explique pourquoi la plupart des extraits de décodage par copier-coller échouent sur de vrais jetons. Si vous décodez en base64 une charge utile contenant un - ou un _ sans le convertir au préalable, vous obtenez un résultat tronqué ou une erreur. Nous traitons cela explicitement ci-dessous. Si vous souhaitez voir les mécanismes d'encodage seuls, notre encodeur et décodeur base64 vous permet d'expérimenter avec les sorties standard et sécurisées pour les URL.
Comment décoder un token JWT en JavaScript#
Le navigateur vous fournit atob pour le décodage base64. L'astuce consiste d'abord à convertir le base64url en base64 standard, puis à gérer correctement l'Unicode. Voici la version complète et correcte, pas le one-liner cassé que l'on trouve habituellement.
Étape 1 : Diviser le token sur les points#
Le token est header.payload.signature. Divisez-le et récupérez les parties souhaitées.
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIn0.signature_here";
const [headerB64, payloadB64, signatureB64] = token.split(".");
Si token.split(".") ne renvoie pas exactement trois parties, la chaîne n'est pas un JWT bien formé et vous devriez vous arrêter plutôt que de décoder des données erronées.
Étape 2 : Convertir le base64url en base64 standard#
C'est l'étape que presque tous les extraits rapides ignorent, et c'est exactement pourquoi ces extraits échouent sur des tokens de production contenant - ou _.
function base64UrlToBase64(input) {
// Remplacer les caractères URL-safe par les caractères base64 standard
let output = input.replace(/-/g, "+").replace(/_/g, "/");
// Rajouter le padding que base64url a supprimé
const pad = output.length % 4;
if (pad) {
output += "=".repeat(4 - pad);
}
return output;
}
Sans restaurer le padding, certains environnements lèvent une InvalidCharacterError sur atob. Avec cette correction, vous décodez de manière fiable à chaque fois.
Étape 3 : Décoder avec atob et analyser le JSON#
Maintenant atob fonctionne, mais il y a un autre piège. atob renvoie une chaîne binaire, donc tout caractère non ASCII (noms accentués, emoji, écritures non latines) sera déformé. Décodez les octets en UTF-8 pour être sûr.
function decodeJwtPart(part) {
const base64 = base64UrlToBase64(part);
const binary = atob(base64);
// Convertir la chaîne binaire en une chaîne UTF-8 correcte
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
const json = new TextDecoder("utf-8").decode(bytes);
return JSON.parse(json);
}
const header = decodeJwtPart(headerB64);
const payload = decodeJwtPart(payloadB64);
console.log(header); // { alg: "HS256", typ: "JWT" }
console.log(payload); // { sub: "1234567890", name: "Jane Doe", ... }
Étape 4 : Lire les revendications qui vous intéressent#
Le payload est maintenant un objet JavaScript normal. Extrayez les revendications standard :
console.log("Sujet :", payload.sub);
console.log("Émis le :", new Date(payload.iat * 1000).toISOString());
console.log("Expire le :", new Date(payload.exp * 1000).toISOString());
console.log("Expiré ?", payload.exp * 1000 < Date.now());
Notez que iat et exp sont des timestamps Unix en secondes, donc multipliez par 1000 avant de les passer à Date. Oublier cela donne des timestamps en 1970 et un après-midi très confus.
Une note sur Node.js#
Dans Node, atob existe dans les versions modernes, mais le décodage idiomatique utilise Buffer, qui gère base64url et UTF-8 en une seule fois :
const payload = JSON.parse(
Buffer.from(payloadB64, "base64url").toString("utf8")
);
Le drapeau d'encodage "base64url" effectue l'échange de caractères et le padding pour vous, ce qui explique pourquoi le code Node semble plus propre que le code navigateur pour cette tâche.
Décoder n'est pas vérifier : l'erreur qui cause les failles de sécurité#
Voici la partie que les extraits de code ne mentionnent jamais, et c'est la chose la plus importante de cette page. Décoder un JWT ne fait que lire les revendications. Cela ne confirme en rien que ces revendications sont vraies.
N'importe qui peut prendre un token, modifier le payload (par exemple, passer "role": "user" à "role": "admin"), le réencoder et le renvoyer à votre serveur. Le payload décodé semblera parfaitement valide. La seule chose qui s'interpose entre ce token falsifié et vos données est la vérification de la signature, qui nécessite la clé secrète ou publique et que le décodage n'utilise jamais.
| Action | Nécessite une clé ? | Ce qu'elle prouve |
|---|---|---|
| Décoder l'en-tête/le payload | Non | Ce que le token prétend dire |
| Vérifier la signature | Oui | Que le token a été émis par vous et n'a pas été modifié |
Vérifier exp / nbf | Non (mais seulement après vérification) | Que le token est dans sa fenêtre de validité |
La règle est simple et absolue :
Ne faites jamais confiance à un payload JWT décodé pour prendre une décision de sécurité. Décodez-le uniquement pour l'affichage et le débogage. Sur le serveur, vérifiez toujours la signature avec une bibliothèque de confiance avant d'agir sur une quelconque revendication.
Un token décodé est une revendication, pas un fait. Traiter le payload comme une vérité absolue parce qu'il "a l'air correct" est exactement ainsi que les bugs d'escalade de privilèges sont introduits. Pour une analyse complète des raisons pour lesquelles ce sont deux opérations différentes et où les équipes se font piéger, lisez notre guide sur le décodage par rapport à la vérification dans la sécurité JWT.
"Un JWT est-il chiffré ?" Non, et c'est important#
C'est l'idée fausse à l'origine de la plupart des erreurs JWT. Un JWT signé standard (un JWS) est encodé, pas chiffré. Le payload est entièrement lisible par quiconque intercepte le token. La signature protège l'intégrité (personne ne peut le modifier sans être détecté), pas la confidentialité (tout le monde peut le lire).
Les conséquences pratiques :
- Ne mettez pas de secrets dans un payload JWT. Pas de mots de passe, pas de clés API, pas de données personnelles que vous n'afficheriez pas sur un panneau d'affichage. Il suffit d'un décodage base64url pour le lire.
- Si vous avez réellement besoin de confidentialité, vous voulez JWE (JSON Web Encryption), un format différent et moins courant qui chiffre réellement le payload.
- Considérez que chaque JWT que vous émettez sera inspecté. Les logs, les outils de développement du navigateur, les proxies et tout décodeur peuvent le lire.
Lecture des revendications standard#
Le payload peut contenir n'importe quoi, mais un ensemble de revendications enregistrées a des significations définies. Les connaître vous aide à déboguer rapidement les problèmes d'authentification.
| Revendication | Nom | Signification |
|---|---|---|
iss | Émetteur | Qui a créé et signé le jeton |
sub | Sujet | À qui se rapporte le jeton, généralement un identifiant utilisateur |
aud | Audience | À qui le jeton est destiné |
exp | Expiration | Temps Unix (secondes) après lequel le jeton est invalide |
nbf | Pas avant | Temps Unix avant lequel le jeton est invalide |
iat | Émis à | Temps Unix de création du jeton |
jti | ID JWT | Un identifiant unique pour le jeton |
Lorsque vous cherchez la cause d'un bug "pourquoi cet utilisateur est-il déconnecté", décodez le jeton et vérifiez d'abord exp. Une exp expirée ou un problème de décalage d'horloge avec nbf est le coupable le plus courant, et vous pouvez le repérer en quelques secondes une fois le payload lisible.
Quand utiliser un outil de décodage à la place#
Écrire la fonction de décodage vaut le coup une fois pour comprendre le mécanisme. Pour le débogage quotidien, coller une fonction dans la console pour chaque jeton est lent, et un mauvais copier-coller réintroduit le bug base64url que vous venez de corriger.
Un décodeur basé sur le navigateur est plus rapide et plus sûr pour l'inspection :
- Il sépare, convertit et affiche l'en-tête et la charge utile instantanément.
- Il signale un
expexpiré pour que vous n'ayez pas à faire le calcul du timestamp. - Il s'exécute dans votre navigateur, donc le jeton n'est pas envoyé à un serveur (important, car le jeton est un identifiant de session actif).
Ce dernier point est plus important que les gens ne le pensent. Un JWT est souvent un jeton de session actif. Le coller dans un décodeur douteux en ligne qui l'envoie à un serveur revient à remettre des identifiants fonctionnels. Notre décodeur JWT gratuit décode entièrement dans votre navigateur, affiche l'en-tête, la charge utile et une date d'expiration lisible, et ne transmet jamais le jeton. Pour le contexte de sécurité sur ce qu'un décodeur peut et ne peut pas vous dire, notre guide de sécurité des jetons et décodeur JWT approfondit les bonnes pratiques d'inspection sécurisée.
Comment décoder un jeton JWT en toute sécurité : le résumé#
Pour décoder un jeton JWT, divisez-le aux points, convertissez chaque partie base64url en base64 standard (remplacez - et _, restaurez le remplissage), exécutez atob, puis analysez le JSON. En Node, Buffer.from(part, "base64url") effectue la conversion pour vous. L'en-tête vous indique l'algorithme, la charge utile contient vos revendications, et iat et exp sont des secondes depuis 1970.
Le point non négociable à retenir : décoder, c'est lire, pas faire confiance. Une charge utile JWT est publique, réversible et falsifiable. Décodez-la librement pour l'affichage et le débogage, mais prenez toute décision d'autorisation réelle sur le serveur après avoir vérifié la signature avec une clé. Comprenez cette distinction et les JWT sont simples. L'ignorez et vous créez une faille de sécurité qui ressemble à du code fonctionnel.
Foire aux questions#
Comment décoder un token JWT sans bibliothèque ?
Séparez le token par ses points en header, payload et signature, puis décodez le header et le payload en base64url. Dans le navigateur, convertissez le base64url en base64 (remplacez - par +, _ par /, et rajoutez le remplissage =) avant d'appeler atob, puis JSON.parse le résultat. Dans Node, Buffer.from(part, "base64url").toString("utf8") le fait en une ligne.
Pourquoi mon appel atob échoue-t-il sur un JWT ?
Car les JWT utilisent le base64url, pas le base64 standard. Ils contiennent - et _ au lieu de + et /, et ils suppriment le remplissage =. Passer cette chaîne brute à atob génère une erreur ou renvoie des données erronées. Convertissez d'abord les caractères et restaurez le remplissage, c'est l'étape que la plupart des extraits rapides omettent.
Un JWT est-il chiffré ou simplement encodé ? Un JWT signé standard est encodé, pas chiffré. Le payload est en base64url, que n'importe qui peut inverser sans clé, donc traitez son contenu comme public. La signature protège contre la falsification, pas contre la lecture. Si vous avez besoin que le payload soit confidentiel, utilisez JWE (JSON Web Encryption) à la place.
Quelqu'un peut-il lire ou modifier mon payload JWT ? Toute personne possédant le token peut lire instantanément le payload, car il est simplement en base64url. Elle peut aussi le modifier et le ré-encoder. Ce qu'elle ne peut pas faire, c'est produire une signature valide sans votre clé secrète ou privée, c'est pourquoi votre serveur doit vérifier la signature plutôt que de se fier aux revendications décodées.
Décoder un JWT vérifie-t-il qu'il est valide ?
Non. Décoder ne fait que lire ce que le token prétend. La vérification est une étape séparée qui contrôle la signature par rapport à votre clé pour confirmer que le token est authentique et non modifié, puis vérifie exp et nbf pour les dates. Ne prenez jamais de décision d'autorisation basée uniquement sur un payload décodé.
Que signifient iat et exp dans un JWT ?
Ce sont des horodatages. iat (issued at) est la date de création du token, et exp (expiration) est la date à laquelle il cesse d'être valide. Les deux sont en temps Unix en secondes, donc multipliez par 1000 avant de construire un objet Date JavaScript. Un token est expiré quand exp * 1000 est inférieur à l'heure actuelle.



