Le malentendu le plus frequent que j’entends sur JWT est: ce jeton est chiffre avec JWT.

Il n’est pas chiffre. Le header et le payload d’un JWT sont codes en Base64URL, la meme famille que Base64. Toute personne qui possede la chaine peut la coller dans n’importe quel decodeur JWT en ligne et lire immediatement l’identifiant utilisateur, la date d’expiration et la liste des roles. Cet article existe pour que la prochaine fois que vous lisiez chiffre avec JWT vous puissiez dire tout de suite: c’est signe, pas chiffre.

Vue d’ensemble en 30 secondes

  • Un JWT (JSON Web Token) comporte generalement trois parties: header.payload.signature, separees par des points.
  • Le header et le payload sont codes en Base64URL (RFC 4648). Ils sont reversibles et entierement lisibles.
  • La signature est une signature numerique que le serveur produit sur les deux premieres parties avec un secret ou une cle privee. Elle prouve que le jeton n’a pas ete modifie. Elle ne prouve pas que le contenu est confidentiel.
  • Ne mettez jamais de mots de passe, cles API ou autres secrets dans le payload. Pour proteger un secret, utilisez un vrai chiffrement (AES, libsodium, age, GPG).

A quoi ressemble un vrai JWT

L’outil Decodeur JWT dispose d’un bouton Load sample qui produit un JWT standard en trois parties. L’outil affiche chacune des trois parties decodees a droite, ainsi qu’une note de securite: signature not verified. Cette note est l’etat normal, car le decodage seul ne verifie jamais la signature.

Concretement, le header decode contient les champs alg et typ. alg est l’algorithme de signature, typ vaut generalement JWT. Le payload decode contient des claims comme iss, sub, aud, iat, exp, jti et role, qui representent respectivement l’emetteur, le sujet, l’audience, la date d’emission, la date d’expiration, l’id du jeton et le role.

5 scenarios reels ou JWT est mal utilise

Scenario 1: mettre le mot de passe utilisateur dans le payload

Exemple de payload: un champ comme password: Hunter2 dans le payload du JWT.

Pourquoi les gens tombent dans le piege: ils supposent qu’un jeton signe par le serveur est automatiquement sur. En pratique, toute personne qui obtient le jeton (extension de navigateur, log client, header Referer, cache CDN) peut le coller dans jwt.io ou dans notre Decodeur JWT et lire le champ password en une seconde. La signature empeche la falsification, pas la lecture.

Solution: les mots de passe ne vont jamais dans un JWT. Les champs sensibles vont dans un canal chiffre separe (chiffrement cote serveur, un vault, etc.) et seuls les identifiants et roles non sensibles vont dans les claims.

Scenario 2: mettre une cle API ou une chaine de connexion dans le payload

Exemple de payload: un champ comme api_key: … dans les claims.

Mettre une cle API dans le payload est la meme classe d’erreur que d’y mettre un mot de passe. Un attaquant qui vole le JWT de n’importe quel utilisateur peut lire la cle API backend directement dans le payload, puis appeler n’importe quel endpoint interne sans passer par l’authentification.

Solution: les cles API, chaines de connexion et jetons sont des secrets et doivent vivre sur le serveur. Si le client doit en referencer un, passez par un endpoint d’authentification dedie qui emet un bearer token de courte duree.

Scenario 3: mettre des champs prives de l’utilisateur dans le payload

Exemple de payload: des champs comme email, phone et ssn_last4 fourres dans les claims.

JWT sert a transmettre des claims entre services, et le chemin intermediaire (passerelles, logs, CDN) voit souvent le jeton complet. Ecrire des champs prives dans le payload equivaut a diffuser ces champs a travers tous vos systemes et viole le RGPD et les lois de confidentialite similaires.

Solution: gardez le JWT au minimum d’identite necessaire: sub, role, exp, iss, aud. Laissez le destinataire chercher les champs prives dans sa propre base avec sub.

Scenario 4: mettre alg a none ou ne pas valider l’algorithme

Exemple de header: alg: none, ou une signature vide.

C’est l’attaque classique. L’attaquant change alg en none, vide la signature et forge un payload qui dit admin. Si le serveur n’applique pas strictement l’algorithme, la requete passe tranquillement. Notre Decodeur JWT affiche un avertissement en rouge quand alg vaut none ou que la signature est vide: les systemes en production devraient generalement le refuser.

Solution: le serveur doit utiliser une liste blanche stricte d’algorithmes (par exemple, seulement RS256 ou HS256) et comparer le champ alg a cette liste. Ne choisissez jamais l’algorithme de verification dynamiquement a partir du header. C’est un schema classique de CVE.

Scenario 5: croire que exp est encore valide parce que le jeton se decode

Exemple de payload: un champ comme exp: 1700000000 (secondes) que la bibliotheque traite comme des millisecondes, ou une horloge serveur qui a derive.

Si l’horloge serveur est desynchronisee, l’horloge client a derive, ou l’unite iat/exp est fausse (millisecondes au lieu de secondes), vous obtenez l’inverse de ce que vous attendez: un jeton dont exp n’est pas encore arrive est rejete, ou un jeton dont exp est passe depuis longtemps est accepte.

Solution: le serveur utilise toujours son propre temps, il ne fait jamais confiance aux headers du client. Si l’outil affiche iat tres loin dans le futur, ou si exp ressemble a une valeur de 13 chiffres en millisecondes au lieu de 10 chiffres en secondes, il leve un avertissement millisecondTimestamp. C’est pourquoi passer un jeton dans le Decodeur JWT avant de se plonger dans un probleme d’auth vous fait gagner des heures.

4 faits contre-intuitifs

Fait 1: une chaine JWT est 30 a 50% plus longue que le JSON d’origine

Le codage Base64URL gonfle la taille. Trois bytes deviennent quatre caracteres, et plus le payload est gros, plus le surcout est important. Sur des claims courts c’est invisible, mais quelques KB de payload font la difference.

Fait 2: la signature ne vous dit pas que le contenu est authentique

La signature prouve une seule chose: le serveur l’a signee avec son secret, donc changer un seul byte casse la signature. Elle ne prouve pas qui a signe, si le signataire est de confiance, ou si la cle de signature a fuitee.

La verification est le travail du serveur, pas du client. Apres avoir recu un jeton, tout ce que le client peut faire c’est le renvoyer au serveur pour verification. C’est exactement pour cela que notre outil indique explicitement que la signature n’est pas verifiee.

Fait 3: les bibliotheques des differents langages ne se comportent pas pareil par defaut

  • jsonwebtoken (Node.js): accepte plusieurs algorithmes par defaut. En production vous devez passer une liste blanche explicite comme algorithms: [RS256].
  • PyJWT (Python): strict par defaut. Il ne valide que si vous passez un argument algorithms= explicite.
  • java-jwt (Java): permissif par defaut. Vous devez specifier algorithms a la main.

Quand vous traversez une frontiere entre langages, un coup d’oeil rapide au comportement par defaut de l’autre bibliotheque evite la majorite des CVE.

Fait 4: JWT expire = rejete par le serveur, pas par le client

Le claim exp est pour le serveur. Meme si le client voit un jeton dont l’exp est encore dans le futur, le serveur peut quand meme le rejeter. Le serveur peut imposer une duree de vie plus courte, forcer un refresh, ou invalider activement le jeton selon d’autres politiques (changement d’IP, changement de role, revocation manuelle).

C’est pourquoi un statut qui semble valide dans le Decodeur JWT ne garantit pas que le serveur acceptera le jeton.

Pratiques recommandees

  1. Gardez le payload au minimum: sub, iss, aud, exp, iat, jti, role. Pas de mots de passe, cles API, donnees privees ou chaines de connexion.
  2. Avant de verifier, figez une liste blanche d’algorithmes (par exemple algorithms: [RS256]) et ne choisissez jamais dynamiquement a partir du header.
  3. Laissez le serveur generer exp lui-meme, ne faites jamais confiance au champ exp du client. Un client qui modifie le payload et le re-signe n’y arrive pas (la signature ne correspondra pas), mais ne comptez pas la-dessus.
  4. Pour debugger un probleme d’auth, utilisez le Decodeur JWT hors ligne pour lire les claims. Ne collez jamais un vrai jeton dans un site qui l’envoie. Notre outil tourne 100% en local: rien ne sort du navigateur et aucun log n’est garde.
  5. Pour proteger un secret, utilisez un vrai chiffrement. JWT ne resout pas la confidentialite, seulement l’authentification et la resistance a la falsification. Pour les secrets, utilisez AES-GCM, libsodium ou age.
  6. JWT n’est pas une session. Il est sans etat, mais ce n’est pas pour autant revocable. Pour invalider des jetons, le serveur doit maintenir une denylist, ou utiliser des durees courtes avec des refresh tokens.

JWT c’est signature plus encodage, pas chiffrement. Quand vous avez besoin de confidentialite, utilisez le chiffrement. Quand vous avez besoin d’authentification, JWT est l’outil adapte.

Essayez notre Decodeur JWT — cliquez sur Load sample pour generer un JWT standard, puis Decode pour voir les segments header, payload et signature, ainsi que la note rouge signature-not-verified. Pour comparer Base64 au vrai chiffrement, lisez notre article Base64 n’est pas du chiffrement et l’outil Encodeur de texte.