Pour décoder un JWT, décodez en base64url ses deux premières parties séparées par des points afin de lire l'en-tête et le payload en JSON. Le décodage est une simple lecture : il ignore la signature, donc il vous indique ce que les revendications disent, mais pas si elles sont authentiques. La vérification est l'étape distincte qui prouve que le jeton n'a pas été falsifié.
Cette distinction est le cœur du sujet. Un JWT est signé, pas chiffré, ce qui signifie que toute personne possédant le jeton peut lire chaque revendication sans clé. Considérer « Je l'ai décodé et les revendications semblent correctes » comme une preuve d'authenticité est ainsi que de vraies vulnérabilités s'infiltrent en production. Décortiquons un jeton, voyons exactement où le décodage s'arrête et où la vérification commence, puis couvrons les attaques qui exploitent cet écart.
Ce que contient réellement un JWT#
Un JSON Web Token est composé de trois sections encodées en base64url, séparées par des points :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFkYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTczNTY4OTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Les trois parties sont en-tête.charge.signature. Décodez les deux premières et vous obtenez du JSON lisible :
// En-tête
{ "alg": "HS256", "typ": "JWT" }
// Charge (les revendications)
{ "sub": "1234", "name": "Ada", "role": "admin", "exp": 1735689600 }
alg indique l'algorithme utilisé pour signer le jeton (ici HMAC-SHA256). La charge contient les revendications : sub (sujet/identifiant utilisateur), role et exp (expiration sous forme de timestamp Unix). La troisième partie, la signature, est un hachage avec clé des deux premières. Attention : la charge n'est que du base64url, c'est-à-dire un encodage, pas un chiffrement. Ne mettez jamais de secret dans la charge d'un JWT. N'importe qui peut la lire.
Le base64url est une variante du base64 adaptée aux URL : il remplace
+et/par-et_et supprime le remplissage. Si vous avez déjà essayé de décoder un JWT avec un outil base64 standard et obtenu une erreur, c'est généralement à cause du jeu de caractères.
Decode vs Verify : la distinction qui compte#
Ces deux opérations se ressemblent et sont constamment confondues dans les tutoriels. Elles ne sont pas identiques, et les mélanger constitue un bug de sécurité.
| Decode | Verify | |
|---|---|---|
| Ce qu'il fait | Lit l'en-tête et le payload | Confirme que la signature est valide |
| Nécessite une clé ? | Non | Oui (clé secrète ou publique) |
| Un inconnu peut-il le faire ? | Oui, avec n'importe quel outil base64 | Non, seulement le correspondant du signataire |
| Prouve l'authenticité ? | Non | Oui |
| Quand l'utiliser | Débogage, inspection des revendications | Chaque fois que vous faites confiance au token |
Voici la différence en code. Le décodage est trivial et sans confiance :
// DECODE UNIQUEMENT : lit les revendications, ne prouve rien
function decodeJwt(token) {
const [header, payload] = token.split(".");
const json = (part) =>
JSON.parse(atob(part.replace(/-/g, "+").replace(/_/g, "/")));
return { header: json(header), payload: json(payload) };
}
const { payload } = decodeJwt(token);
console.log(payload.role); // "admin" ... mais ce token est-il même réel ?
Ce code renvoie joyeusement role: "admin" pour n'importe quel token que vous lui donnez, y compris un token falsifié il y a cinq secondes par un attaquant. La vérification est ce qui rend les revendications dignes de confiance :
// VERIFY : vérifie la signature et l'expiration avant de faire confiance
import jwt from "jsonwebtoken";
try {
const claims = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ["HS256"], // épinglez l'algorithme explicitement
});
// Ce n'est qu'à ce moment que claims.role est sûr à utiliser
grantAccess(claims.role);
} catch (err) {
// Signature invalide, token expiré ou algorithme non autorisé
denyAccess();
}
La règle est simple : decodez pour regarder, vérifiez pour faire confiance. Tout chemin de code qui prend une décision d'autorisation doit vérifier. Décoder sans vérifier sur le serveur équivaut à laisser les utilisateurs modifier leurs propres permissions.
Les attaques qui exploitent la faille#
Lorsque les développeurs sautent ou mal gèrent la vérification, trois attaques classiques apparaissent. Toutes exploitent le fait que le jeton est contrôlé par l'attaquant jusqu'à ce que la signature soit correctement vérifiée.
L'attaque par algorithme "none"#
La spécification JWT inclut une valeur alg de none, signifiant "non signé". Si un serveur lit l'algorithme depuis l'en-tête du jeton et lui fait confiance, un attaquant peut supprimer la signature, définir "alg": "none" et forger n'importe quelle charge utile :
// En-tête forgé par l'attaquant
{ "alg": "none", "typ": "JWT" }
// Charge utile forgée par l'attaquant : admin instantané
{ "sub": "9999", "role": "admin" }
Avec la signature supprimée et alg défini sur none, un vérificateur naïf qui "honore" l'en-tête l'accepte. La solution est de ne jamais laisser le jeton dicter son propre algorithme. Fixez l'algorithme attendu côté serveur, exactement comme l'option algorithms: ["HS256"] ci-dessus, afin que none et toute autre valeur inattendue soient rejetés catégoriquement.
L'attaque par confusion d'algorithme (RS256 vers HS256)#
Celle-ci est plus subtile et a touché des bibliothèques majeures. Supposons que votre système signe les jetons avec RS256 (asymétrique : une clé privée signe, une clé publique vérifie). La clé publique est, par définition, publique.
Un attaquant prend cette clé publique, change l'en-tête en "alg": "HS256" (HMAC symétrique) et signe un jeton forgé en utilisant la clé publique comme secret HMAC. Un vérificateur vulnérable qui choisit l'algorithme depuis l'en-tête exécutera alors la vérification HMAC en utilisant la clé publique qu'il possède déjà, et la signature forgée sera valide. L'attaquant vient d'utiliser votre clé de vérification publiée pour signer des jetons.
La défense est le même principe : fixez l'algorithme. Si votre serveur attend RS256, configurez-le pour n'accepter que RS256 et vérifiez avec la clé publique en tant que clé RSA, jamais comme un secret HMAC. Permettre à l'en-tête de choisir est la cause racine à chaque fois.
Ignorer l'expiration et autres revendications#
Même une signature parfaitement vérifiée ne signifie pas que le jeton est actuellement valide. La vérification confirme que le jeton a été émis par vous et n'a pas été modifié ; elle ne vérifie pas, en soi, que le jeton est toujours valide ou destiné à ce public. Validez toujours :
exp(expiration) etnbf(pas avant) pour rejeter les jetons périmés ou futurs.aud(audience) pour qu'un jeton émis pour un service ne puisse pas être rejoué contre un autre.iss(émetteur) pour n'accepter que les jetons du fournisseur d'identité de confiance.
La plupart des bibliothèques vérifient exp automatiquement lorsque vous appelez verify, mais aud et iss nécessitent généralement que vous passiez les valeurs attendues explicitement.
L'endroit où vous stockez et envoyez le jeton compte aussi#
La vérification protège le contenu du jeton, mais la manipulation du jeton constitue sa propre surface d'attaque. Quelques règles non négociables :
- Privilégiez les cookies HTTP-only plutôt que
localStoragepour les jetons de session, afin que les scripts côté client (et toute charge utile XSS) ne puissent pas les lire. - Utilisez toujours HTTPS pour que le jeton ne soit pas intercepté en transit.
- Gardez les charges utiles minimales et non sensibles. Comme n'importe qui peut décoder la charge utile, n'y stockez jamais de mots de passe, d'informations personnelles complètes ou de secrets.
- Utilisez des durées de vie courtes avec des jetons d'actualisation pour qu'un jeton d'accès divulgué expire rapidement.
Un jeton divulgué reste valide jusqu'à son expiration. C'est pourquoi les fenêtres d'expiration et l'hygiène de stockage sont aussi importantes que la cryptographie de signature.
Comment décoder un JWT en toute sécurité pour le débogage#
Vous aurez constamment besoin d'inspecter des tokens lors de la construction d'une authentification : vérifier les revendications émises par un fournisseur d'identité, déboguer une erreur 401, confirmer une date d'expiration. Le problème est qu'un JWT est un identifiant actif. Coller un vrai token de session sur un site web aléatoire signifie que le serveur de ce site possède désormais un token valide pour votre compte.
Étape 1 : Utilisez un décodeur qui s'exécute dans votre navigateur#
Utilisez un décodeur JWT gratuit qui effectue tout le parsing côté client. Lorsque le décodage se fait entièrement dans l'onglet du navigateur, le token n'est jamais transmis, donc même un token de production reste sur votre machine. C'est l'habitude la plus importante à prendre lors de la manipulation de vrais tokens : confirmez que l'outil est côté client avant de coller.
Étape 2 : Lisez l'en-tête, le payload et l'expiration#
Le décodeur sépare le token et affiche l'en-tête et le payload en JSON formaté, ainsi qu'une expiration lisible pour éviter de convertir manuellement un timestamp Unix. Vérifiez l'alg, examinez les revendications, et confirmez que exp est dans le futur. Rappelez-vous que vous lisez, pas validez, à ce stade.
Étape 3 : Vérifiez la signature uniquement avec un secret non lié à la production#
Si le décodeur et vérificateur JWT permet de tester une signature, ne collez jamais qu'un secret de test ou de développement, jamais une clé de signature de production, dans un formulaire web. Les calculs de signature peuvent aussi être reproduits localement : une signature HS256 est simplement un HMAC, que vous pouvez calculer avec un générateur de hachage, et les parties base64url se décodent avec un encodeur et décodeur Base64. Pour une vue d'ensemble sur la manipulation sécurisée des tokens, consultez notre guide sur les décodeurs JWT et la sécurité des tokens.
L'essentiel#
Décoder un JWT lit les revendications ; le vérifier prouve leur authenticité. Ce sont des opérations différentes avec des exigences distinctes, et les confondre est à l'origine de l'attaque par algorithme none et des attaques de confusion d'algorithme. Côté serveur, vérifiez chaque jeton, fixez explicitement l'algorithme et contrôlez exp, aud et iss. Pour le débogage, décodez localement afin qu'un identifiant actif ne quitte jamais votre navigateur.
Foire aux questions#
Comment décoder un JWT ? Divisez le jeton en trois parties à l'aide de ses deux points, puis décodez en base64url la première partie (en-tête) et la deuxième partie (charge utile) en JSON. La troisième partie est la signature et n'est pas destinée à être lue. Le décodage ne nécessite pas de clé et vous montre uniquement les revendications, il ne confirme pas l'authenticité du jeton.
Quelle est la différence entre décoder et vérifier un JWT ? Le décodage lit l'en-tête et la charge utile sans clé, donc tout le monde peut le faire. La vérification recalcule la signature avec la clé secrète ou publique pour confirmer que le jeton a été émis par vous et n'a pas été modifié. Le décodage vous indique ce que disent les revendications ; seule la vérification vous dit si vous devez leur faire confiance.
Est-il sûr de décoder un JWT en ligne ? Seulement avec un outil qui décode entièrement dans votre navigateur. Un JWT est un identifiant actif, donc coller un jeton de session réel dans un site qui l'envoie à un serveur donne à ce serveur un jeton valide pour votre compte. Utilisez un décodeur côté client pour que le jeton ne quitte jamais votre onglet.
Qu'est-ce que l'attaque par algorithme "none" sur JWT ?
Elle exploite les serveurs qui lisent l'algorithme de signature depuis l'en-tête du jeton. Un attaquant définit "alg": "none", supprime la signature et forge n'importe quelle charge utile. Un vérificateur qui honore l'en-tête accepte le jeton non signé. La solution est de fixer l'algorithme attendu sur le serveur et de rejeter catégoriquement none.
Quelqu'un peut-il lire les données à l'intérieur de mon JWT ? Oui. Un JWT est signé, pas chiffré, donc la charge utile est seulement encodée en base64url et toute personne possédant le jeton peut décoder et lire chaque revendication. Ne stockez jamais de mots de passe, secrets ou données personnelles sensibles dans une charge utile JWT. La signature protège contre la falsification, pas contre la lecture.
Comment vérifier si un JWT a expiré ?
Décodez la charge utile et lisez la revendication exp, un timestamp Unix en secondes ; s'il est dans le passé, le jeton a expiré. Lors de la vérification réelle, la plupart des bibliothèques rejettent automatiquement les jetons expirés lorsque vous appelez verify. Un décodeur qui affiche une date d'expiration lisible vous permet de le confirmer rapidement lors du débogage.



