Aller au contenu
Toova
Tous les outils

Qu'est-ce que JWT et comment le décoder en toute sécurité

Toova

Les JSON Web Tokens apparaissent partout dans le développement web moderne — en-têtes d'authentification, flux OAuth, clés API, gestion de session. Si vous avez travaillé avec une API nécessitant un jeton Bearer, vous avez déjà utilisé un JWT. Mais la plupart des développeurs les traitent comme des blocs opaques et regardent rarement à l'intérieur. Comprendre la structure d'un JWT, ce que signifie chaque partie et comment en décoder un sans faire d'erreurs de sécurité prend environ quinze minutes. Ce guide couvre tout cela.

L'anatomie d'un JWT

Chaque JWT est une chaîne de trois segments encodés en Base64URL séparés par des points :

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQ3MDAwMDAwLCJpYXQiOjE3NDY5OTY0MDAsInNjb3BlIjoicmVhZDp1c2VycyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Le format est en-tête.charge-utile.signature. Chaque segment encode des informations différentes.

L'en-tête

Le premier segment est l'en-tête. Décodé, l'exemple ci-dessus devient :

{
  "alg": "RS256",
  "typ": "JWT"
}

alg spécifie l'algorithme utilisé pour signer le jeton — RS256 signifie RSA avec SHA-256. typ identifie le type de jeton. Ces deux champs sont le minimum ; certains jetons incluent également kid (identifiant de clé) pour indiquer au vérificateur quelle clé publique utiliser lorsque plusieurs clés sont en rotation.

La charge utile

Le deuxième segment est la charge utile — les données réelles. Décodée :

{
  "sub": "user_123",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1747000000,
  "iat": 1746996400,
  "scope": "read:users"
}

La charge utile contient des revendications : des déclarations sur l'entité que le jeton représente. Certaines revendications sont standardisées (appelées revendications enregistrées) ; d'autres sont spécifiques à l'application (appelées revendications privées). La charge utile est encodée en Base64URL, pas chiffrée. Quiconque obtient la chaîne du jeton peut lire chaque revendication à l'intérieur.

La signature

Le troisième segment est la signature. Elle est produite en prenant l'en-tête et la charge utile encodés, en les concaténant avec un point, puis en signant le résultat avec la clé secrète ou la clé privée spécifiée dans alg :

signature = sign(
  base64url(header) + "." + base64url(payload),
  secretOrPrivateKey
)

La signature permet à toute partie possédant la clé publique correspondante (ou la clé secrète, pour les algorithmes symétriques) de vérifier que le jeton a été émis par une source de confiance et que ni l'en-tête ni la charge utile n'ont été modifiés après l'émission. Un seul caractère modifié dans la charge utile invalide entièrement la signature.

Comment décoder un JWT

Le décodage est différent de la vérification. Le décodage lit simplement les données à l'intérieur d'un jeton. La vérification confirme que le jeton est authentique et non expiré. Vous devriez toujours vérifier dans le code de production ; le décodage seul est utile pour le débogage.

Étape 1 : Diviser le jeton

Divisez la chaîne JWT sur le caractère .. Vous obtiendrez trois segments. Si vous en obtenez moins de trois, le jeton est malformé.

Étape 2 : Décoder l'en-tête et la charge utile

Décodez en Base64URL chacun des deux premiers segments. Base64URL est comme le Base64 standard mais utilise - à la place de + et _ à la place de /, sans caractères de rembourrage. Une fois décodé, vous pouvez analyser chaque segment comme JSON.

Étape 3 : Analyser et inspecter

Lisez les revendications. Vérifiez exp (expiration) par rapport au timestamp Unix actuel. Regardez iss et aud pour confirmer que le jeton est destiné à votre application. Ne faites confiance à aucune revendication avant d'avoir vérifié la signature.

Pour une inspection rapide pendant le développement, utilisez le Décodeur JWT. Il s'exécute entièrement dans votre navigateur — votre jeton ne quitte jamais votre appareil. Vous pouvez aussi décoder manuellement les segments base64 avec l'encodeur/décodeur Base64 si vous souhaitez voir les octets bruts. Pour vérifier les signatures dans les scripts de test, le générateur HMAC vous permet de reproduire des signatures HS256 sans écrire de code.

Étape 4 : Vérifier la signature dans le code

En production, vérifiez toujours la signature en utilisant une bibliothèque conçue pour votre langage. N'implémentez pas la vérification de signature à la main. Les options populaires incluent jose ou jsonwebtoken pour Node.js, PyJWT pour Python, golang-jwt/jwt pour Go, et nimbus-jose-jwt pour Java.

Passez toujours l'algorithme explicitement. Ne laissez jamais la bibliothèque déduire l'algorithme depuis l'en-tête du jeton.

Revendications standard (revendications enregistrées)

La RFC 7519 définit un ensemble de noms de revendications enregistrées. Elles ne sont pas requises, mais lorsqu'elles sont présentes, elles doivent suivre la sémantique standard.

iss — Émetteur (Issuer)

L'entité qui a créé et signé le jeton. Généralement une URL identifiant le serveur d'autorisation (par exemple, https://auth.example.com). Les vérificateurs devraient vérifier que iss correspond à une valeur attendue et rejeter les jetons d'émetteurs inconnus.

sub — Sujet (Subject)

Le principal que le jeton représente — généralement un ID d'utilisateur, un nom de compte de service ou un identifiant d'appareil. La valeur doit être unique dans le contexte de l'émetteur. Votre application utilise sub pour identifier à quel utilisateur appartient la requête.

aud — Audience

Le(s) destinataire(s) prévu(s) du jeton. Si l'identifiant de votre API est https://api.example.com, les jetons émis pour d'autres services doivent être rejetés. Ne pas valider aud permet aux attaquants de réutiliser un jeton obtenu d'un service dans un autre service.

exp — Heure d'expiration (Expiration Time)

Un timestamp Unix après lequel le jeton ne doit pas être accepté. Validez toujours exp. Un jeton sans expiration vit effectivement éternellement — s'il est volé, l'attaquant a un accès indéfini.

iat — Émis à (Issued At)

Le timestamp Unix quand le jeton a été créé. Utile pour détecter les jetons techniquement non expirés mais suspicieusement anciens. Vous pouvez utiliser iat pour imposer un âge maximum du jeton indépendamment de exp.

nbf — Pas avant (Not Before)

Le timestamp Unix avant lequel le jeton ne doit pas être accepté. Moins courant que exp, mais utile lors de l'émission de jetons à l'avance — par exemple, une tâche planifiée qui ne devrait pas démarrer avant une heure spécifique.

jti — ID JWT (JWT ID)

Un identifiant unique pour le jeton. Permet aux émetteurs de prévenir les attaques de rejeu en stockant les valeurs jti utilisées et en rejetant les jetons avec des ID en double. Nécessaire lorsque vous avez besoin de jetons à usage unique, comme les liens de réinitialisation de mot de passe.

Pièges de sécurité

Les implémentations JWT ont une histoire de vulnérabilités subtiles. Voici les plus importants à comprendre avant de livrer du code qui accepte des JWT.

L'attaque « alg: none »

Certaines bibliothèques JWT des premiers jours de la norme acceptaient un jeton qui spécifiait "alg": "none" dans son en-tête. Un attaquant pouvait prendre n'importe quel jeton valide, remplacer l'algorithme par none, supprimer la signature, et la bibliothèque l'acceptait comme entièrement valide — aucune signature requise.

La solution : spécifiez toujours l'algorithme attendu explicitement lorsque vous appelez votre fonction de vérification. Traitez tout jeton affirmant alg: none comme invalide. La plupart des bibliothèques modernes ont corrigé cela, mais vaut la peine de confirmer les défauts de votre bibliothèque avant de passer en production.

Confusion d'algorithme (dégradation RS256 vers HS256)

Certaines bibliothèques qui supportaient les deux algorithmes utilisaient le champ alg dans l'en-tête du jeton pour décider comment vérifier. Un attaquant pouvait changer l'algorithme en HS256, puis signer le jeton en utilisant la clé publique du serveur comme secret HMAC. Le serveur, voyant HS256, vérifiait avec la clé publique et acceptait le jeton forgé.

La solution est la même : verrouillez l'algorithme dans votre code de vérification. Ne laissez jamais l'en-tête du jeton influencer l'algorithme utilisé pour la vérification.

Accepter des jetons expirés

Ne pas valider exp est une omission courante — parfois introduite lorsque les développeurs ajoutent une période de grâce qui croît sans limite, ou lorsque le chemin de code de validation est contourné dans une branche de code. Traitez un jeton expiré de la même manière qu'un jeton manquant : rejetez-le avec un 401 et exigez que le client se réauthentifie ou utilise un jeton de rafraîchissement.

Données sensibles dans la charge utile

La charge utile est encodée en Base64URL, pas chiffrée. Quiconque intercepte le jeton peut la décoder. Ne mettez pas de mots de passe, de numéros de carte de crédit, de numéros de sécurité sociale ou d'autres données sensibles dans la charge utile. Si vous avez besoin de transmettre des revendications sensibles en toute sécurité, utilisez un jeton JWE (JSON Web Encryption), qui chiffre la charge utile. Les JWT signés avec JWS (le cas courant) ne garantissent que l'authenticité, pas la confidentialité.

Validation d'audience manquante

Ignorer la validation de aud signifie qu'un jeton émis pour le Service A peut être rejoué au Service B — tant que les deux services partagent la même clé de signature ou font confiance au même émetteur. Dans les architectures multi-services, validez toujours que l'audience du jeton correspond à l'identifiant de votre service.

Meilleures pratiques pour 2026

Choisir RS256 pour la plupart des applications

RS256 utilise une clé privée pour signer et une clé publique pour vérifier. Seul le serveur d'autorisation détient la clé privée. N'importe quel service peut vérifier les jetons en utilisant la clé publique, qui peut être publiée ouvertement (souvent via un endpoint JWKS). Si un service individuel est compromis, les attaquants acquièrent la capacité de vérifier les jetons — mais pas d'en forger de nouveaux.

HS256 utilise un seul secret partagé à la fois pour la signature et la vérification. Tout service pouvant vérifier les jetons peut également en créer. Dans une configuration de microservices, partager le secret entre de nombreux services augmente le rayon d'explosion d'une violation.

Garder les jetons d'accès à courte durée de vie

Il n'y a pas de mécanisme de révocation intégré pour les JWT — une fois émis, un jeton est valide jusqu'à son expiration (sauf si vous implémentez une liste de blocage, ce qui réintroduit l'état côté serveur). Des délais d'expiration courts (15 minutes à 1 heure) limitent la fenêtre de dommage si un jeton est volé. Associez les jetons d'accès avec des jetons de rafraîchissement à longue durée de vie stockés dans des cookies HttpOnly sécurisés, pas dans localStorage.

Utiliser la rotation des jetons de rafraîchissement

Lorsqu'un client utilise un jeton de rafraîchissement pour obtenir un nouveau jeton d'accès, émettez un nouveau jeton de rafraîchissement et invalidez l'ancien. De cette façon, un jeton de rafraîchissement volé est détecté la prochaine fois que le client légitime essaie d'utiliser l'original : le serveur voit un jeton de rafraîchissement réutilisé et peut révoquer toute la session. Ce schéma est décrit dans la RFC 6819 et est largement supporté par les serveurs d'autorisation modernes.

Publier un endpoint JWKS

Un endpoint JSON Web Key Set (JWKS) (/.well-known/jwks.json par convention) publie vos clés publiques de signature dans un format standard. D'autres services peuvent récupérer le JWKS et vérifier les jetons sans recevoir les clés hors bande. Cela rend également la rotation des clés simple : ajoutez la nouvelle clé au JWKS, commencez à signer avec elle, puis supprimez l'ancienne clé une fois que tous les jetons existants ont expiré.

Rotation périodique des clés

Même si votre clé privée n'est jamais compromise, la rotation périodique des clés limite la fenêtre pendant laquelle une clé volée pourrait être utilisée pour forger des jetons. Utilisez kid dans vos en-têtes JWT pour référencer la clé de signature, afin que les vérificateurs puissent sélectionner la bonne clé publique dans votre JWKS sans casser les jetons existants lors d'une rotation.

Valider chaque revendication

Vérifiez iss, aud, exp et nbf à chaque requête. Ne sautez aucun d'eux parce qu'ils semblent redondants dans votre configuration — les conditions qui les font paraître inutiles aujourd'hui sont exactement les conditions qui changent lors d'un incident.

JWT vs. cookies de session

Les JWT et les sessions côté serveur résolvent le même problème — persister l'état d'authentification entre des requêtes HTTP sans état — mais avec des compromis différents.

Les JWT sont autonomes. Le serveur n'a pas besoin d'interroger une base de données pour valider une requête. Cela les rend attrayants pour les systèmes distribués et les APIs consommées par des clients mobiles ou tiers. L'inconvénient est que la révocation nécessite une liste de blocage (ce qui réintroduit l'état côté serveur) ou de tolérer la durée de vie restante d'un jeton compromis.

Les cookies de session sont validés côté serveur. Le serveur stocke l'état de session et peut révoquer l'accès instantanément en supprimant la session. Les cookies avec les drapeaux HttpOnly et Secure sont protégés contre l'accès JavaScript et l'interception réseau. L'inconvénient est que chaque requête nécessite une interrogation de base de données, ce qui peut devenir un goulot d'étranglement à grande échelle.

Pour les applications web traditionnelles avec des pages rendues côté serveur et un seul backend, les cookies de session restent un choix solide et plus simple. Pour les APIs consommées par plusieurs clients, les architectures de microservices et les applications mobiles, les JWT avec une courte expiration et la rotation des jetons de rafraîchissement sont l'option la plus pratique.

Décodez votre jeton

La façon la plus rapide d'inspecter un JWT est de le coller dans le Décodeur JWT. Il divise le jeton, décode l'en-tête et la charge utile, et rend les revendications dans un format lisible — sans envoyer votre jeton nulle part. Si vous avez besoin d'encoder ou de décoder des chaînes Base64URL brutes manuellement, l'encodeur/décodeur Base64 gère les variantes standard et URL-safe. Pour vérifier des signatures HMAC dans des scripts de test, le générateur HMAC vous permet de reproduire une signature HS256 et de la comparer à ce que contient votre jeton.

Pour la spécification complète, consultez la RFC 7519 (JWT) et les exemples interactifs sur jwt.io.