Comment décoder un JWT : guide complet pour développeurs
Votre API vient de renvoyer 401 Unauthorized. L’en-tête Authorization: Bearer eyJhbGciOi... a l’air correct. Le token était-il expiré, l’audience incorrecte, ou l’émetteur a-t-il fait tourner une clé ? Impossible de répondre sans lire ce qui se trouve dans le token, et pour décoder un JWT, vous n’avez besoin ni de secret, ni de bibliothèque, ni même de connexion réseau. Un JWT, c’est trois segments encodés en base64url et reliés par des points. Le décodage est purement mécanique : découper, base64url, JSON.parse. Pas de magie, pas de cryptographie.
Ce guide parcourt l’anatomie du JWT, montre comment le décoder en Node.js, Python, Go et dans le navigateur, clarifie la distinction décodage/vérification qui piège la plupart des équipes, et liste les vraies causes d’échec. Si vous avez juste besoin d’inspecter un token tout de suite, passez directement à notre décodeur JWT gratuit. Il tourne entièrement dans votre navigateur, donc vos tokens de production ne quittent jamais votre machine.
Qu’est-ce qu’un JWT ? (anatomie rapide)
Un JSON Web Token (JWT) est une credential compacte et URL-safe définie dans la RFC 7519. Il transporte des claims, c’est-à-dire des données sur l’utilisateur et sur le token lui-même, entre deux parties. Un JWT, ce sont trois segments encodés en base64url et reliés par des points : un header, un payload et une signature.
Voici un vrai token éclaté pour faire apparaître la structure :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header
.
eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0 ← payload
.
4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0 ← signature
Le header décrit comment le token est signé, en général { "alg": "HS256", "typ": "JWT" }. Le payload porte les claims, à la fois les claims enregistrés comme sub, exp, iat et des claims personnalisés comme role ou tenant. La signature est une preuve cryptographique calculée sur le header et le payload qui permet au destinataire de détecter toute altération. Base64url est une variante URL-safe de base64 ; voyez notre guide Base64 pour débutants pour un tour d’horizon en dix minutes.
Vous croiserez des JWT partout où vit l’authentification moderne : access tokens OAuth 2.0, ID tokens OpenID Connect, credentials API émises par Auth0, Okta, Clerk, Supabase, Firebase, et tokens échangés entre microservices dans un mesh. C’est devenu le format de credential par défaut de la plupart des stacks actuelles.
Une phrase à retenir avant d’aller plus loin : les JWT sont encodés, pas chiffrés. Toute personne qui détient le token peut lire chacun de ses claims. La signature prouve l’origine, elle ne cache pas le contenu. Ce seul fait conditionne tout le reste : ce qu’il est prudent de mettre dans le payload, pourquoi le décodage ne nécessite aucun secret, pourquoi la vérification de signature n’est pas négociable côté serveur.
Comment fonctionne le décodage JWT (base64url, pas du déchiffrement)
Décoder un JWT n’est pas une opération cryptographique. C’est une séquence de quatre étapes simples :
- Découper le token sur
.en exactement trois segments. - Décoder le premier segment en base64url et le parser comme JSON. C’est le header.
- Décoder le deuxième segment en base64url et le parser comme JSON. C’est le payload.
- Laisser le troisième segment (la signature) sous forme d’octets bruts. Le vérifier exige la clé.
Voilà toute la procédure. Aucune bibliothèque n’est obligatoire. N’importe quel langage doté d’un base64 et d’un parseur JSON peut décoder un JWT en cinq lignes. Notre encodeur/décodeur Base64 effectue les étapes 2 et 3 à la main si vous voulez voir la mécanique à l’œuvre.
Qu’est-ce que base64url ?
Base64url, c’est du base64 classique avec trois ajustements pour rester sûr dans les URL et les en-têtes HTTP : - remplace +, _ remplace /, et le padding = final est supprimé. Si vous envoyez du base64url brut à un décodeur base64 standard sans inverser ces substitutions, vous obtenez soit du charabia, soit une erreur. Le guide Base64 avancé détaille les cas limites de padding.
| Base64 standard | base64url | |
|---|---|---|
| Alphabet | A-Z a-z 0-9 + / | A-Z a-z 0-9 - _ |
| Padding | = obligatoire à la fin | Supprimé |
| URL-safe ? | Non | Oui |
| Exemple | PDw/Pz8+ | PDw_Pz8- |
Un point qui vaut d’être souligné : vous ne pouvez pas déchiffrer la signature côté client. Le décodage va dans un seul sens, des octets encodés vers le JSON. Vérifier la signature est une opération distincte qui exige soit le secret HMAC (pour les algorithmes de la famille HS), soit la clé publique de l’émetteur (pour RS, PS, ES, EdDSA).
Pourquoi vous n’avez pas besoin du secret pour décoder
Parce que le payload est du base64url plus du JSON, pas du texte chiffré. Un secret n’entre en jeu que lorsque vous voulez prouver que le token n’a pas été altéré, c’est-à-dire la vérification de signature. N’importe quel acteur sur le chemin réseau, quiconque possède le token dans une ligne de log, quiconque dispose d’un navigateur peut lire tous les claims que vous y avez mis. C’est pourquoi vous ne devez jamais placer de mots de passe, de clés API ou de PII au-delà de ce que le destinataire connaît déjà dans un payload JWT. Pour le modèle de menace plus large, lisez notre guide des bonnes pratiques de sécurité.
Décodez un JWT en ligne en 3 clics — décodeur JWT gratuit
Parfois, il vous faut juste une réponse immédiate : ce token est-il expiré, le claim aud est-il bien ce que je crois, le header dit-il alg:none ? Le chemin le plus court passe par notre décodeur JWT en ligne. Il est conçu pour les interventions d’incident à 2 h du matin.
- Collez le token complet dans la zone de saisie. Incluez les trois segments séparés par des points.
- Lisez le header, le payload et les badges de statut en haut : algorithme, date d’émission, expiration, et un badge rouge
Expiredsiexpest déjà dans le passé. - Copiez le panneau dont vous avez besoin dans votre rapport de bug, votre fil Slack ou votre fixture de test.
Pourquoi c’est sûr pour de vrais tokens de production :
- 100 % navigateur. Le décodage s’appuie sur
atobnatif etJSON.parse. Aucune requête réseau, jamais. - Aucun log, aucun tracking, aucun cookie, aucune inscription.
- Fonctionne hors ligne une fois la page chargée.
Le décodeur JWT fonctionne quel que soit l’algorithme. Puisque le décodage ne réclame que base64url et JSON, il lit toutes les variantes de JWS : HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA et alg:none. Seule la vérification de signature dépend de l’algorithme, et la vérification n’est pas un service que vous souhaitez confier à un outil web public (on y revient plus bas).
Besoin de décoder manuellement un segment en base64 pour recouper l’outil ? Utilisez notre encodeur/décodeur Base64 et passez-lui chaque segment en base64url.
Comment décoder un JWT en code (Node.js, Python, Go, navigateur)
Pour tout ce qui ne relève pas du débogage interactif (middleware, tests, scripts de migration, outils CLI), vous passerez par une bibliothèque. Voici le code minimal pour décoder un JWT dans les quatre environnements les plus probables, avec le chemin lecture seule et le chemin vérification côte à côte. Chaque extrait est copiable-collable et produit les sorties affichées dans les commentaires.
Décoder un JWT en Node.js (jsonwebtoken)
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' +
'.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0' +
'.4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0';
// Décodage seul — ne vérifie PAS la signature
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header); // { alg: 'HS256', typ: 'JWT' }
console.log(decoded.payload); // { sub: 'user_123', exp: 1999999999 }
// Vérification — le chemin de production
const secret = process.env.JWT_SECRET;
const verified = jwt.verify(token, secret, { algorithms: ['HS256'] });
Passez toujours une allowlist algorithms explicite à verify. L’omettre a, par le passé, permis à des attaquants de rétrograder un token RS256 en HS256 en signant avec la clé publique comme secret HMAC, c’est la fameuse attaque par confusion d’algorithme. L’allowlist est votre défense.
Décoder un JWT en Python (PyJWT)
# pip install PyJWT
import jwt
token = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0"
".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
)
# Décodage seul — à bannir pour l'authentification, bien pour l'inspection
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded) # {'sub': 'user_123', 'exp': 1999999999}
# Header sans toucher au payload
header = jwt.get_unverified_header(token)
print(header) # {'alg': 'HS256', 'typ': 'JWT'}
# Vérification — chemin de production
payload = jwt.decode(
token,
key="your-hs256-secret",
algorithms=["HS256"],
audience="api.example.com",
)
PyJWT refuse de vérifier sans liste algorithms, un défaut judicieux qui prévient l’attaque de confusion évoquée dans l’exemple Node.
Décoder un JWT en Go (golang-jwt/jwt/v5)
// go get github.com/golang-jwt/jwt/v5
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
func main() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0" +
".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
// Décodage seul
parser := jwt.NewParser()
claims := jwt.MapClaims{}
_, _, err := parser.ParseUnverified(tokenString, claims)
if err != nil {
panic(err)
}
fmt.Println(claims["sub"], claims["exp"]) // user_123 1.999999999e+09
// Vérification
secret := []byte("your-hs256-secret")
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
}
return secret, nil
})
fmt.Println(token.Valid, err)
}
La closure keyFunc est l’endroit où vous imposez la famille d’algorithmes. Rejetez tout ce qui n’est pas la méthode attendue avant de rendre la clé.
Décoder un JWT dans le navigateur (zéro dépendance)
Parfois, vous ne voulez aucune dépendance : un panneau de debug rapide, une extension de navigateur, un petit badge d’UI qui affiche le rôle de l’utilisateur courant. Les API natives du navigateur suffisent :
function decodeJwt(token) {
const [h, p] = token.split('.');
const pad = (s) => s + '==='.slice((s.length + 3) % 4);
const decodeSegment = (s) => {
const b64 = pad(s).replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
};
return { header: decodeSegment(h), payload: decodeSegment(p) };
}
const { header, payload } = decodeJwt(token);
console.log(header); // { alg: 'HS256', typ: 'JWT' }
console.log(payload); // { sub: 'user_123', exp: 1999999999 }
Le passage par TextDecoder est indispensable dès que le payload contient du non-ASCII (un emoji dans un nom affiché, du cyrillique dans un preferred_username). Un simple atob renvoie une chaîne binaire, ce qui casse JSON.parse sur l’UTF-8 multi-octets. C’est exactement ce que notre décodeur JWT en ligne exécute localement dans votre navigateur, sans l’interface graphique.
Tableau comparatif
| Langage | Décodage seul | Vérification | Bibliothèque |
|---|---|---|---|
| Node.js | jwt.decode(token) | jwt.verify(token, key, { algorithms: [...] }) | jsonwebtoken |
| Python | jwt.decode(token, options={"verify_signature": False}) | jwt.decode(token, key, algorithms=[...]) | PyJWT |
| Go | parser.ParseUnverified(token, claims) | jwt.Parse(token, keyFunc) | golang-jwt/jwt/v5 |
| Navigateur | atob + TextDecoder + JSON.parse | Demandez à votre backend | — |
Décoder vs vérifier — la distinction critique
Décoder un JWT lit ses claims. Vérifier un JWT prouve que ces claims n’ont pas été altérés. Le décodage ne fait jamais confiance, la vérification est la confiance. C’est la distinction qui sépare une implémentation d’authentification fonctionnelle d’une CVE.
| Décoder | Vérifier | |
|---|---|---|
| Besoin d’un secret/d’une clé ? | Non | Oui |
| Peut tourner côté client ? | Sans risque | Jamais |
| Prouve l’authenticité ? | Non | Oui |
| Contrôle l’expiration ? | Facultatif | Oui |
| Cas d’usage | Debug, inspection | Authentification, autorisation |
Ne prenez jamais une décision d’autorisation à partir d’un JWT décodé (non vérifié). Ni dans un middleware, ni dans un hook React, ni dans une fonction serverless que vous croyez être derrière la passerelle. Les claims décodés disent ce que le token prétend, les claims vérifiés disent ce que l’émetteur a signé. Un attaquant qui soumet à votre serveur un token forgé à la main sans signature valide peut écrire n’importe quoi dans le payload, et seule la vérification de signature le rejette.
Un détail qui a son poids sur la famille HMAC : si vous utilisez HS256, l’entropie de votre secret fait tout. Un secret court et devinable se fait casser hors ligne par brute force contre n’importe quel token capturé par un attaquant, qui émet ensuite ses propres tokens et entre par la porte d’entrée. Utilisez au moins 256 bits de véritable aléa. Voyez notre guide sur la solidité des secrets HMAC pour les calculs qui expliquent pourquoi ça compte.
Référence des claims JWT usuels
Chaque JWT que vous rencontrez utilise un sous-ensemble des claims enregistrés de la RFC 7519. Mémorisez cette courte liste :
| Claim | Nom | Exemple | Notes |
|---|---|---|---|
iss | Issuer | https://auth.example.com | Qui a émis le token |
sub | Subject | user_123 | En général l’identifiant utilisateur |
aud | Audience | api.example.com | À qui le token est destiné, doit correspondre côté serveur |
exp | Expiration | 1715003600 | Secondes Unix ; passé = expiré |
iat | Issued At | 1715000000 | Secondes Unix d’émission du token |
nbf | Not Before | 1715000060 | Premier instant où le token est utilisable |
jti | JWT ID | d1f8… | Unique par token ; empêche le rejeu |
kid | Key ID (header) | key-2025-01 | Quelle clé de votre JWKS a signé ce token |
Des claims spécifiques à l’application cohabitent avec ceux-là : role, scope, email, tenant_id, ce que votre fournisseur d’identité émet. Gardez-les courts, chaque octet voyage avec chaque requête.
Pour lire des dates humaines à partir de iat et exp, essayez notre convertisseur de timestamp Unix. Collez le nombre, obtenez la date dans votre fuseau horaire, repérez un bug de décalage en une seconde.
Dépannage : pourquoi mon JWT ne se décode-t-il pas ?
Cinq vraies causes d’échec, par fréquence approximative. Chacune est présentée Symptôme → Cause → Correction.
- « Format JWT invalide, trois segments attendus. » Vous n’avez copié que le payload, ou le shell a enroulé le token sur plusieurs lignes et vous n’avez attrapé que la première. Correction : recopiez la valeur
xxx.yyy.zzzcomplète depuis le corps de la réponse originale, et non depuis un terminal qui la restitue. Les longues valeurs sur une seule ligne survivent mieux dans l’onglet Network des devtools que dans un terminal qui défile. - Cinq segments au lieu de trois. Vous avez un JWE (JWT chiffré), pas un JWS. Le format est
header.encryptedKey.iv.ciphertext.tag. Un décodeur lira le header, mais le payload est chiffré. Correction : décoder le payload exige la clé de déchiffrement, en général gérée côté serveur par votre SDK d’authentification, pas par un outil de debug. - Erreur base64url sur un token qui semble pourtant valide. Le token a été URL-encodé quelque part sur le chemin de copie (un cookie, une URL de redirection, un log de proxy capturé). Vous voyez des
%2Eou%2Blittéraux dans la chaîne. Correction : URL-décodez-le d’abord, puis passez le résultat au décodeur JWT. - Erreur de parsing JSON sur le payload. Un terminal ou un client de chat a inséré des retours à la ligne de soft-wrap, ou un script a collé des guillemets typographiques autour d’un identifiant. Correction : consultez les octets bruts de la réponse (curl avec
-o file.txt, ou la vue Raw des devtools), supprimez les espaces, collez à nouveau. - Se décode proprement mais le backend le rejette quand même. Ce n’est pas un problème de décodage, c’est un problème de vérification. Le token est structurellement valide ; quelque chose que le serveur contrôle (signature,
aud,exp, décalage d’horloge, recherche dekid) échoue. Passez à la section suivante.
Deux mentions honorables qui ne sont pas des erreurs de parsing mais méritent d’être repérées pendant que vous avez le décodeur ouvert : une valeur alg à none dans le header (à traiter comme hostile en production) et une valeur exp dans le passé. Le décodeur affiche quand même les claims pour que vous puissiez déboguer, c’est le comportement attendu, et notre outil le signale d’un badge rouge Expired.
Quand décoder ne suffit pas : la vérification de signature
Le décodage s’arrête à « voici ce que le token prétend ». La vérification, c’est ce qui transforme une prétention en décision de confiance. La signature est une preuve, calculée avec la clé privée de l’émetteur ou un secret partagé, qui lie le header et le payload : changez un seul octet et la vérification échoue. Sans ce contrôle, quiconque peut faire un POST sur votre endpoint peut fabriquer à la main un token « admin » en éditant le payload et en sautant complètement la signature.
N’acceptez jamais alg:none.
La vérification en production, quel que soit le langage ou le framework, ressemble à peu près à cette checklist. Tout manquement est un bug :
- Passez une allowlist
algorithms: ['RS256']explicite (ou celle que vous utilisez). Elle bloque les attaques par confusion d’algorithme. - Vérifiez que
audcorrespond à l’identifiant de votre service et queisscorrespond à l’URL d’émetteur attendue. - Contrôlez
expcontre l’heure courante avec au plus 60 secondes de tolérance de décalage d’horloge. - Lors d’une rotation de clé, cherchez la clé publique via
kidsur un endpoint JWKS. Ne codez jamais en dur une clé unique pour l’éternité. - Révoquez efficacement en maintenant
expcourt (minutes, pas jours) et, éventuellement, en tenant une denylist dejtipour les tokens à forte valeur.
Toute bibliothèque JWT sérieuse expose ces réglages comme options d’un seul appel. Si votre code de vérification ne les fixe pas, vous laissez tourner les valeurs par défaut, et les valeurs par défaut ont historiquement été le bug. Pour le modèle de menace complet, voyez notre guide des bonnes pratiques de sécurité.
FAQ
Peut-on décoder un JWT sans la clé secrète ?
Oui. Le header et le payload sont encodés en base64url, pas chiffrés, donc quiconque détient le token peut lire ses claims. Le secret ou la clé publique n’est nécessaire que pour vérifier la signature. C’est voulu : le payload est fait pour être lisible afin que le destinataire puisse prendre des décisions d’autorisation.
Est-il sûr de coller mon JWT de production dans un décodeur en ligne ?
Uniquement si le décodeur tourne dans votre navigateur et n’envoie jamais le token. Notre décodeur JWT parse localement via atob natif et JSON.parse, rien n’est envoyé à aucun serveur. Les décodeurs distants qui POSTent votre token à une API doivent être considérés comme des fuites de credential.
Quelle est la différence entre décoder et vérifier un JWT ?
Décoder ne fait que lire les claims : aucune clé requise, aucune preuve produite. Vérifier contrôle la signature avec la clé de l’émetteur et confirme que le token n’a pas été altéré. Ne prenez jamais une décision d’authentification à partir d’un token décodé mais non vérifié.
Mon JWT paraît tronqué, qu’est-ce qui compte comme un format valide ?
Un JWT valide comporte exactement trois segments base64url séparés par des points : header.payload.signature. Cinq segments signifient que vous avez un JWT chiffré (JWE), pas un JWS. Zéro point signifie que vous n’avez copié qu’un seul segment d’une ligne de terminal enroulée.
Pourquoi le décodeur affiche-t-il quand même un token expiré ?
Un décodeur lit les claims indépendamment de la validité pour vous permettre de déboguer les rejets. Seul un vérificateur refuse les tokens expirés. Notre outil fait remonter un badge Expired en comparant exp à votre horloge locale, ce qui vous permet de repérer le problème instantanément sans plisser les yeux sur des timestamps Unix.
Quels algorithmes puis-je décoder ?
Tous. Le décodage ne réclame que base64url et du parsing JSON, il fonctionne quel que soit l’algorithme. Cela inclut HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA et alg:none. Seule la vérification dépend de l’algorithme choisi.
Dois-je utiliser jwt-decode ou jsonwebtoken en Node.js ?
Utilisez jwt-decode côté frontend quand vous avez seulement besoin de lire le payload, par exemple pour afficher un nom d’utilisateur depuis un access token. Utilisez jsonwebtoken côté backend, car seul le backend peut détenir la clé de signature et exécuter jwt.verify. Ne vérifiez jamais côté client.
Conclusion
Décoder un JWT est loin d’être aussi mystérieux que ne le laisse entendre l’expression « token cryptographique ». Retenez ces cinq points et vous ne resterez plus jamais bloqué à fixer une chaîne opaque eyJhbGciOi… :
- Décoder, c’est base64url plus parsing JSON. Aucun secret requis.
- Un JWT a trois parties (header, payload, signature) reliées par des points.
- Décoder ne prouve jamais l’authenticité. Vérifiez toujours côté serveur avec la clé de l’émetteur.
- Refusez
alg:noneet passez toujours une allowlist d’algorithmes explicite àverify. - Ne stockez jamais de mots de passe, de clés privées ou de PII sensibles dans le payload, tout cela est lisible par quiconque détient le token.
Mettez notre décodeur JWT gratuit en favori pour vos interventions d’astreinte. Collez un token, lisez les claims, repérez les expirations en une seconde, et tout cela sans que votre token ne quitte jamais le navigateur.