Signer et vérifier les JWT
Un JWT est composé de trois segments base64url — en-tête, charge utile, signature — et les deux premiers sont lisibles par quiconque détient le jeton. Décoder un jeton vous dit ce qu’il prétend ; seul le troisième segment vous dit s’il faut le croire. Cet article porte sur ce troisième segment : comment une signature est fabriquée, qui peut en fabriquer une, comment les vérificateurs la contrôlent, et le point exact où « vérifié » cesse de signifier « digne de confiance ». Vous pouvez exécuter chaque étape vous-même dans le signataire et vérificateur JWT, qui signe et vérifie localement dans votre navigateur.
Décoder n’est pas vérifier
Le malentendu le plus courant sur les JWT est aussi le plus dangereux : croire que lire un jeton revient à le contrôler. Ce n’est pas le cas. L’en-tête et la charge utile sont en base64url — un encodage, pas un chiffrement — donc quiconque intercepte un jeton, en trouve un dans un journal ou le copie depuis le stockage local d’un navigateur peut le décoder en quelques secondes, sans aucune clé. Les revendications qu’il contient (pour qui il est destiné, quand il expire, quels rôles il affirme) sont des affirmations, et les affirmations ne coûtent rien.
La signature existe précisément parce que les revendications ne coûtent rien. C’est un lien cryptographique entre l’en-tête, la charge utile et une clé que seul l’émetteur détient : changez un caractère de la charge utile et la signature cesse de correspondre. Vérifier, c’est rejouer ces mathématiques — recalculer la signature sur l’en-tête et la charge utile reçus, avec la clé de l’émetteur, et comparer. Si elle correspond, le jeton est exactement tel que l’émetteur l’a signé ; sinon, quelque chose a changé, et le jeton ne vaut rien, aussi plausibles que ses revendications paraissent.
Symétrique ou asymétrique : qui peut forger un jeton
Avec HS256, le secret qui signe est le même que celui qui vérifie. Cela rend HMAC rapide, simple et tout à fait suffisant au sein d’un même service — votre API signe ses propres jetons et les contrôle elle-même, et le secret ne quitte jamais le bâtiment. Dès qu’une deuxième partie doit vérifier, le modèle se brise : lui remettre le secret, c’est lui remettre le pouvoir de forger des jetons, parce que vérifier et signer sont la même opération. Un secret HMAC divulgué n’est pas un problème de vérification ; c’est un problème de contrefaçon.
Les algorithmes asymétriques séparent les deux pouvoirs. RS256, ES256 et EdDSA signent avec une clé privée et vérifient avec une clé publique, si bien que l’émetteur peut publier ses clés publiques — sous forme de JWKS — et que chaque vérificateur au monde peut contrôler les jetons sans jamais détenir le pouvoir d’en forger un. Cette séparation est ce qui fait fonctionner les JWT entre services : votre API fait confiance aux jetons d’un fournisseur d’identité parce qu’elle fait confiance aux clés publiées par ce fournisseur, et non parce qu’elle partage un secret avec lui. Le prix à payer est la complexité — formats de clés, rotation, correspondance des kid — et c’est ce que le signataire et vérificateur de ce site existe pour rendre concret.
L’attaque par confusion d’algorithme
L’attaque JWT classique n’a besoin d’aucune cryptographie cassée — il lui faut un vérificateur qui laisse le jeton nommer son propre algorithme. L’attaquant prend un jeton RS256 légitime, dont la clé publique est publiée (comme le sont les clés publiques), et re-signe la même charge utile avec HS256, en utilisant la clé publique elle-même comme secret HMAC. Un vérificateur qui lit alg dans l’en-tête et choisit sa routine de vérification en conséquence va désormais « vérifier » le jeton falsifié avec HMAC, en utilisant la clé publique comme secret — et la clé publique est publique.
La défense est structurelle, pas consultative : le vérificateur doit figer les algorithmes qu’il accepte, et la famille de la clé doit correspondre à la famille de l’algorithme — une clé RSA ne peut jamais vérifier un jeton HMAC, et un secret ne peut jamais vérifier un jeton RSA. Un vérificateur qui réinterprète une clé d’une famille à l’autre est cassé par conception, aussi correcte que soit sa cryptographie. C’est pourquoi le vérificateur de ce site refuse les clés d’une autre famille avec un avertissement explicite au lieu de tenter le contrôle : le refus est la fonctionnalité.
À quoi ressemblent la JWKS et la rotation des kid en pratique
Un fournisseur d’identité ne distribue pas une seule clé éternelle ; il publie un jeu — une JWKS, généralement à /.well-known/jwks.json — et fait tourner ses clés en en ajoutant une nouvelle puis, après une période de grâce, en retirant l’ancienne. L’en-tête de chaque jeton porte un kid qui nomme la clé qui l’a signé, si bien qu’un vérificateur récupère le jeu, choisit la clé correspondante et vérifie. Les jetons signés avec l’ancienne clé continuent de fonctionner jusqu’à son retrait, et c’est ce qui rend la rotation sûre au lieu d’en faire un jour de bascule.
Ce sont les détails opérationnels qui cassent. Les vérificateurs doivent mettre la JWKS en cache (la récupérer à chaque requête est une panne auto-infligée), gérer le cas où un kid est inconnu — généralement une rotation que le cache n’a pas encore vue, donc récupérer à nouveau avant d’échouer — et ne jamais accepter un jeu de clés depuis une URL en qui on n’a pas confiance : le jeu ne vaut que le canal par lequel il est arrivé, et c’est pourquoi les points de terminaison JWKS vivent derrière HTTPS et pourquoi le constructeur de JWKS ici dérive chaque kid comme une empreinte RFC 7638 de la clé elle-même, pour que l’identifiant ne puisse pas être usurpé indépendamment de la clé.
exp, nbf, iat — et le problème de l’horloge
Une signature prouve qu’un jeton a été signé ; elle ne dit rien sur le fait de savoir s’il doit encore être honoré. C’est le travail des revendications temporelles : exp (quand il cesse d’être valable), nbf (quand il commence), iat (quand il a été émis). Un vérificateur qui contrôle la signature et saute l’horloge accepte un jeton pour toujours — y compris un jeton volé il y a un an. Les durées de vie courtes sont la réponse pratique : des jetons d’accès mesurés en minutes, des jetons de rafraîchissement portant la confiance de longue durée, et la révocation gérée en ne réémettant tout simplement pas.
Les horloges ne sont pas d’accord, donc les vérificateurs tolèrent un peu de marge — généralement une ou deux minutes de tolérance sur exp et nbf — mais la tolérance sert à absorber la dérive, pas à autoriser l’ignorance de l’expiration. Un appareil dont l’horloge est décalée de quelques minutes produira des jetons que son propre serveur rejette, et un jeton avec un iat dans le futur est un signal de falsification, pas une erreur d’arrondi. La même discipline d’horloge s’applique à l’outillage : le vérificateur contrôle l’expiration par rapport à l’horloge que vous pouvez voir, et dit franchement quand un jeton est expiré plutôt que simplement « invalide ».
Ce que « vérifié » prouve et ne prouve pas
Un jeton vérifié prouve trois choses, et rien de plus : l’en-tête et la charge utile sont exactement tels que signés, le signataire détenait la clé, et le jeton est dans sa fenêtre de validité. Il ne prouve pas que l’émetteur est celui que vous croyez — cela dépend d’où vient la clé, et une clé récupérée depuis une URL contrôlée par un attaquant vérifie parfaitement les jetons d’un attaquant. Il ne prouve pas que les revendications sont vraies — une signature authentifie la déclaration, pas les faits derrière elle. Et il ne prouve pas que le jeton n’a pas été volé : un jeton valide présenté par n’importe qui est accepté, et c’est pourquoi les jetons sont de courte durée et à portée limitée.
La décision de confiance vit donc en dehors du jeton. Votre API fait confiance à un jeton parce qu’elle a récupéré la clé depuis l’URL bien connue de son fournisseur d’identité, en HTTPS, parce que l’iss correspond au fournisseur qu’elle a configuré, et parce que l’aud correspond au service qu’elle est. La vérification est le dernier contrôle, pas le premier : provenance de la clé, émetteur, audience, puis signature, puis temps. Un outil qui affiche « VALIDE » sans ce contexte dit la vérité sur les mathématiques et rien sur la confiance — et c’est pourquoi le vérificateur de ce site le dit sur la page, à côté du verdict.
Questions fréquentes
Puis-je vérifier un jeton sans la clé ?
Non — la vérification exige la clé publique de l’émetteur (ou le secret partagé, pour HMAC). Décoder n’exige aucune clé, et c’est exactement la confusion à éviter : un jeton décodé est lisible, pas vérifié. Si l’émetteur publie une JWKS, récupérez-la depuis l’URL bien connue et faites correspondre le kid du jeton ; le vérificateur de ce site le fait pour vous.
Pourquoi mon jeton RS256 échoue-t-il avec mon secret ?
Parce qu’un secret et une clé RSA appartiennent à des familles différentes, et qu’un vérificateur sain refuse de les mélanger. Les jetons RS256 se vérifient avec la clé publique RSA de l’émetteur — PEM, JWK ou JWKS — jamais avec un secret texte. Utiliser un secret serait l’attaque par confusion d’algorithme : la clé publique est publique, donc n’importe qui pourrait forger des jetons si la vérification HMAC l’acceptait comme secret.
Combien de temps un JWT doit-il vivre ?
Des minutes, pas des jours, pour les jetons d’accès : 5 à 15 minutes est la plage courante, avec un jeton de rafraîchissement portant la session plus longue. Une durée de vie courte borne les dégâts d’un jeton volé à la fenêtre avant son expiration, et fait de la révocation une question de ne pas réémettre plutôt que de tenir une liste de blocage. Les jetons qui vivent des semaines sont des cookies de session avec un outillage pire.