Un JWT (JSON Web Token, RFC 7519) est une chaîne de texte en trois parties séparées par des points, en-tête.charge.signature, qui transporte des affirmations (claims) sur un utilisateur ou une session de façon à ce que le destinataire puisse vérifier que personne ne les a modifiées. Les deux premières parties sont du JSON encodé en Base64URL : n’importe qui peut les lire. La troisième est une signature que seul le détenteur de la clé peut produire. C’est pourquoi décoder un JWT n’est pas le valider : lire le contenu est trivial ; lui faire confiance exige de vérifier la signature, l’expiration et l’émetteur.
Cet article parcourt l’anatomie d’un jeton, les claims habituels, comment l’inspecter, les erreurs de sécurité classiques et où le stocker dans une application web.
Anatomie d’un jeton
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFpbXJhbiIsImlhdCI6MTc5MTI4MDAwMCwiZXhwIjoxNzkxMjgzNjAwfQ.<signature>
- En-tête (
{"alg":"HS256","typ":"JWT"}) : l’algorithme de signature et le type de jeton. - Charge utile (
{"sub":"1234","name":"Aimran","iat":1791280000,"exp":1791283600}) : les claims. - Signature : le résultat de la signature de
base64url(en-tête) + "." + base64url(charge)avec l’algorithme de l’en-tête. AvecHS256, c’est un HMAC-SHA256 avec une clé secrète partagée ; avecRS256ouES256, une signature asymétrique faite avec une clé privée et vérifiée avec la clé publique.
L’encodage est Base64URL (variante de Base64 avec - et _ au lieu de + et /, sans remplissage =), dont nous parlons dans qu’est-ce que Base64. Ce n’est pas du chiffrement : la charge utile se lit en une ligne de code.
Les claims habituels
| Claim | Nom | Rôle |
|---|---|---|
iss |
Issuer | Qui a émis le jeton (votre serveur d’authentification). |
sub |
Subject | De qui il parle (l’identifiant de l’utilisateur). |
aud |
Audience | À qui il est destiné (l’API qui doit l’accepter). |
exp |
Expiration | Instant Unix à partir duquel il n’est plus valide. |
nbf |
Not before | Instant Unix avant lequel il n’est pas encore valide. |
iat |
Issued at | Quand il a été émis. |
jti |
JWT ID | Identifiant unique, utile pour révoquer. |
Tous sont facultatifs selon la RFC, mais exp devrait toujours être présent : un jeton sans expiration reste valide pour toujours s’il fuit. Vous pouvez ajouter vos propres claims (role, plan), avec une règle : jamais de données sensibles, car la charge utile est publique pour qui détient le jeton.
Comment inspecter un JWT
Pour voir ce que transporte un jeton, il suffit de décoder les deux premières parties. Le décodeur JWT d’AIMRAN Tools affiche l’en-tête et la charge utile, convertit iat, exp et nbf en dates lisibles et indique si le jeton est expiré ou pas encore valide, le tout dans le navigateur, sans envoyer le jeton à aucun serveur. Si vous collez aussi le secret, il vérifie la signature HMAC. En Node.js, sans bibliothèque :
const [header, payload] = token.split('.');
const decode = (part) => JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
console.log(decode(header), decode(payload));
Ce code lit, il ne valide pas. Pour valider, utilisez une bibliothèque éprouvée (jose ou jsonwebtoken en Node ; PyJWT en Python) qui vérifie la signature avec l’algorithme que vous attendez, l’expiration et l’émetteur.
Les erreurs de sécurité qui reviennent
La RFC 8725, les bonnes pratiques officielles, en couvre la plupart :
- Faire confiance à la charge utile sans vérifier la signature. N’importe qui peut forger un jeton avec
"role":"admin". Si votre API ne vérifie pas la signature, elle vient d’offrir des droits. - Accepter l’algorithme annoncé par l’en-tête. L’attaque classique
alg: noneenvoie un jeton non signé en espérant que le serveur l’accepte. La bibliothèque doit vérifier uniquement les algorithmes que vous configurez. - Confusion HS256/RS256. Si le serveur utilise RS256 (clé publique) et accepte aussi HS256, un attaquant peut signer avec la clé publique comme si c’était le secret HMAC. Même remède : une liste fermée d’algorithmes.
- Secrets HMAC faibles. Un secret court se casse par force brute hors ligne, puisque l’attaquant détient le jeton. Utilisez des secrets aléatoires d’au moins 256 bits.
- Jetons éternels. Pas d’
exp, ou des expirations de plusieurs mois. L’usage : des jetons d’accès de quelques minutes et un jeton de rafraîchissement pour les renouveler. - Données sensibles dans la charge utile. E-mails, numéros d’identité, adresses : la charge utile n’est pas chiffrée. Si vous avez besoin de confidentialité, JWE (jetons chiffrés) existe, mais c’est rarement nécessaire.
Où stocker le jeton dans le navigateur
Deux options, avec des compromis différents :
- Cookie
HttpOnly; Secure; SameSite: JavaScript ne peut pas le lire, donc une XSS ne le vole pas ; le navigateur l’envoie tout seul ; il faut se protéger du CSRF (SameSite=LaxouStrictcouvre la plupart des cas). C’est la recommandation habituelle pour les applications web classiques. localStorageou mémoire : n’importe quel script de la page peut le lire, donc une XSS le compromet totalement. Cela n’a de sens que pour des clients qui ne peuvent pas utiliser de cookies (applications natives) ou si l’API est sur un autre domaine et que le jeton ne vit qu’en mémoire le temps de l’onglet.
Dans les deux cas, servir le site avec une Content-Security-Policy stricte réduit le risque de XSS, qui est la vraie menace derrière ce débat.
JWT face aux sessions classiques
Un JWT permet à l’API de valider l’identité sans interroger une base de données à chaque requête : toute l’information voyage dans le jeton. Cet avantage a un coût : on ne peut pas révoquer un jeton avant son expiration, sauf à maintenir une liste de révocation (ce qui ramène la requête en base). Pour une application à serveur unique, une session traditionnelle (identifiant opaque en cookie, données côté serveur) est plus simple et plus facile à invalider. Les JWT brillent quand plusieurs services doivent accepter la même identité sans partager de stockage de sessions.
Conclusion
Un JWT, ce sont trois blocs Base64URL : en-tête, charge utile et signature. N’importe qui peut lire les deux premiers ; seule la signature, vérifiée avec le bon algorithme et la bonne clé, permet de leur faire confiance. Ajoutez toujours exp, limitez les algorithmes acceptés, ne mettez pas de données sensibles dans la charge utile et stockez-le dans un cookie HttpOnly quand c’est possible. Et quand vous voulez savoir ce que contient un jeton, décodez-le sans crainte : ça, c’est vraiment gratuit.