API Security

JWT : risques et bonnes pratiques - Guide complet 2026

JSON Web Tokens décodés : structure, claims, algos, 12 vulnérabilités classiques (alg none, confusion HMAC/RSA, kid injection), stockage, révocation.

Naim Aouaichia
18 min de lecture
  • API Security
  • JWT
  • OAuth
  • OIDC
  • Cryptographie
  • Authentification
  • Cheat Sheet

Les JSON Web Tokens (JWT) sont devenus le standard de facto pour l'authentification et l'autorisation des API modernes. OAuth 2.0, OIDC, sessions mobile, service-to-service : JWT est partout. Leur apparente simplicité cache une surface d'attaque réelle, régulièrement exploitée : alg: none, confusion HMAC/RSA, kid injection, secrets faibles, révocation impossible. Ce guide détaille la structure, les 12 vulnérabilités classiques, les bonnes pratiques 2026, les pièges de stockage et les alternatives (PASETO, opaque tokens) - pour développeurs back-end, AppSec engineers et pentesters.

1. Structure d'un JWT

1.1 Anatomie

Un JWT est composé de trois parties séparées par des points (.) :

<header_base64>.<payload_base64>.<signature_base64>

Exemple concret :

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI2MDEifQ.
eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyXzEyMzQ1Iiwi
YXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzIwMDAwMDAwLCJpYXQiOjE3MTk5OTY0
MDAsInNjb3BlIjoicmVhZDpwcm9maWxlIHdyaXRlOm9yZGVycyJ9.
Adg1m2b3z...signature...xyz

1.2 Header décodé

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-202601"
}
  • alg : algorithme de signature.
  • typ : type du token (JWT par défaut).
  • kid : identifiant de la clé publique (pour rotation).

1.3 Payload décodé (claims)

{
  "iss": "https://auth.example.com",
  "sub": "user_12345",
  "aud": "api.example.com",
  "exp": 1720000000,
  "iat": 1719996400,
  "scope": "read:profile write:orders"
}

Claims standards (RFC 7519) :

  • iss (Issuer) : qui a émis le token.
  • sub (Subject) : qui est identifié.
  • aud (Audience) : destinataire prévu.
  • exp (Expiration) : Unix timestamp d'expiration.
  • nbf (Not Before) : timestamp avant lequel le token n'est pas valide.
  • iat (Issued At) : timestamp d'émission.
  • jti (JWT ID) : identifiant unique (utile pour révocation).

Claims custom : tout champ ajouté (scope, role, org_id, etc.).

1.4 Signature

La signature protège l'intégrité du header et du payload. Formule :

signature = sign(base64(header) + "." + base64(payload), secret_or_private_key)

Vérifiée côté serveur via :

  • Clé partagée (secret) pour HMAC.
  • Clé publique pour RSA/ECDSA/EdDSA.

Un JWT sans signature valide ne doit jamais être accepté. Toute l'intégrité du modèle JWT repose sur cette règle.

1.5 JWT ≠ chiffré par défaut

Un JWT classique est signé (intégrité), pas chiffré (confidentialité). Le payload est lisible par tout le monde en décodant la base64. Ne jamais mettre de données sensibles en clair dans le payload.

Pour le chiffrement, il existe JWE (JSON Web Encryption), variante moins utilisée qui encapsule un payload chiffré.

2. Algorithmes de signature

2.1 Familles

AlgorithmeFamilleClésUsage recommandé
HS256HMAC + SHA-256Secret partagéM2M interne, petites apps mono-tenant
HS384/HS512HMAC + SHASecret partagéIdem avec hash plus fort
RS256RSA + SHA-256Paire clé privée/publique (2048+ bits)Standard pour OIDC et multi-consumers
RS384/RS512RSA + SHAPaireIdem
ES256ECDSA P-256 + SHA-256PairePlus compact que RSA, performant
ES384/ES512ECDSA P-384/P-521PaireSécurité renforcée
EdDSA (Ed25519)Edwards curvesPaireLe plus moderne, sécurité + performance
PS256/PS384/PS512RSA-PSSPaireRSA avec padding moderne
noneAucunRienJAMAIS EN PRODUCTION

2.2 Recommandations 2026

  • Nouveau projet, multi-consumers : EdDSA (Ed25519) - meilleure sécurité, meilleure performance, clés plus compactes.
  • Écosystème traditionnel : RS256 reste acceptable et universellement supporté.
  • Service interne mono-tenant : HS256 avec secret long (256 bits minimum) peut suffire.
  • ES256 excellent compromis taille/perf.

2.3 Ce qu'il faut bannir

  • none : jamais accepté côté serveur.
  • HS256 avec clé RSA : vulnérabilité de confusion (voir §5.2).
  • HS256 avec secret faible : bruteforce possible.
  • Algorithmes dépréciés : SHA-1 variants, MD5 variants.

3. Cas d'usage JWT

3.1 Authentication tokens (ID Token OIDC)

Token prouvant qui est l'utilisateur. Émis par l'IdP après authentification. Consommé par l'application pour créer la session locale.

Ne pas envoyer en continu aux APIs - c'est le rôle de l'access token.

3.2 Authorization tokens (Access Token OAuth 2.0)

Token envoyé à chaque requête API dans le header Authorization: Bearer <token>. Prouve que le client est autorisé à appeler l'API au nom de l'utilisateur.

Durée de vie courte : 15 minutes à 1 heure maximum.

3.3 Refresh tokens

Token de longue durée (quelques jours à quelques mois) qui permet d'obtenir un nouveau access token sans ré-authentifier l'utilisateur. Émis et stocké côté client en sécurité.

Souvent opaque plutôt que JWT, car la révocation doit être instantanée.

3.4 M2M (machine-to-machine)

OAuth 2.0 Client Credentials Grant : un service obtient un access token via client_id + client_secret (ou mTLS). Utilisé pour communications inter-services.

3.5 Webhooks signés

Certains fournisseurs signent leurs webhooks avec un JWT pour permettre au destinataire de vérifier l'origine et l'intégrité.

3.6 Short-lived signed URLs

URLs pré-signées (S3, Cloud Storage) utilisent souvent des JWT pour prouver l'autorisation sur une durée limitée.

4. Claims - validation obligatoire

Un JWT valide techniquement (signature OK) n'est pas forcément utilisable. La validation des claims est un second filtre obligatoire.

4.1 Les 5 validations non-négociables

  1. Signature valide avec l'algorithme attendu côté serveur.
  2. exp non dépassé (token pas expiré).
  3. nbf atteint si présent (pas utilisé avant sa validité).
  4. iss correspond à l'émetteur attendu (pas un JWT d'un autre IdP).
  5. aud contient l'audience attendue (pas un JWT destiné à une autre API).

4.2 Validations contextuelles

  • sub existe et correspond à un user valide dans votre système.
  • scope contient les scopes nécessaires pour l'endpoint.
  • iat pas trop ancien (anti-replay renforcé).
  • Claims custom selon les besoins métier (org_id, tenant_id, etc.).

4.3 Exemple de validation Python

import jwt
from jwt import PyJWKClient
 
JWKS_URL = "https://auth.example.com/.well-known/jwks.json"
jwks_client = PyJWKClient(JWKS_URL)
 
def validate_token(token: str) -> dict:
    # Récupération de la clé publique par kid
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    
    # Décodage + validation de toutes les checks obligatoires
    payload = jwt.decode(
        token,
        signing_key.key,
        algorithms=["RS256"],       # ne pas accepter autre chose
        audience="api.example.com", # valide aud
        issuer="https://auth.example.com",  # valide iss
        options={
            "verify_signature": True,
            "verify_exp": True,
            "verify_nbf": True,
            "verify_iat": True,
            "require": ["exp", "iss", "aud", "sub"],
        }
    )
    return payload

Point critique : algorithms=["RS256"] est une liste fixe côté serveur. Ne jamais lire l'algorithme du header pour choisir comment vérifier (voir §5.1).

5. Les 12 vulnérabilités JWT classiques

5.1 alg: none accepté

CVE-type : bibliothèques JWT anciennes qui honorent un alg: none dans le header, permettant à un attaquant de forger des tokens sans signature.

Attaque :

{"alg": "none", "typ": "JWT"}.{"sub": "admin", "role": "admin"}.

Sans signature, le token est accepté. L'attaquant devient admin.

Mitigation : lister explicitement les algorithmes acceptés côté serveur (algorithms=["RS256"]), jamais de None ou vide.

5.2 Confusion HMAC / RSA

CVE-type : bibliothèques qui utilisent verify(token, key)key est la clé publique RSA (publique = connue). Si l'attaquant change alg de RS256 à HS256, la bibliothèque utilise la clé publique comme secret HMAC.

Attaque :

  1. Récupérer la clé publique (jwks.json).
  2. Forger un JWT avec alg: HS256 signé via HMAC avec cette clé publique comme secret.
  3. Le serveur vérifie en HMAC avec la même clé publique = signature valide.

Mitigation : fixer l'algorithme accepté côté serveur. algorithms=["RS256"] n'acceptera jamais un HS256.

5.3 Secrets HMAC faibles

Un secret HS256 court ou prédictible (ex. secret, mysecretkey) peut être bruteforcé hors-ligne.

Outils publics : hashcat mode 16500 (JWT). Un secret 8 caractères alphanumériques se casse en heures sur un GPU moderne.

Mitigation : secret HS256 d'au moins 256 bits d'entropie aléatoire (32 octets = 64 hex). Généré via secrets.token_bytes(32) (Python) ou équivalent.

5.4 Pas de validation exp

Token sans expiration vérifiée = token utilisable à vie. Cas classique où la bibliothèque par défaut ne valide pas exp, ou où exp est manquant dans le payload.

Mitigation : exiger exp obligatoire, valider côté serveur. Refuser les tokens sans exp.

5.5 Pas de validation aud

Token émis pour une API A utilisé sur une API B. Si B ne valide pas aud, il accepte.

Cas historique : tokens d'OIDC utilisés pour authentifier vers des APIs qui n'étaient pas l'audience prévue.

Mitigation : chaque API valide aud strictement contre sa propre identité.

5.6 Pas de validation iss

Token d'un IdP attaquant accepté par votre application parce que vous ne vérifiez pas qu'il vient de votre IdP.

Mitigation : validation stricte de iss contre une liste d'émetteurs de confiance.

5.7 kid injection / path traversal

Le header contient un champ kid (key ID). Certaines implémentations l'utilisent pour charger la clé depuis un fichier ou une URL.

Attaque :

{
  "alg": "HS256",
  "kid": "../../../etc/passwd"
}

Si le serveur lit le fichier pointé par kid comme secret HMAC, l'attaquant peut pointer vers un fichier dont il connaît le contenu (ex. un fichier vide, /dev/null) et forger un token avec le même contenu comme secret.

Ou encore : kid pointant vers une URL contrôlée par l'attaquant pour servir une clé arbitraire.

Mitigation : kid doit être validé contre une allowlist côté serveur, jamais utilisé comme path/URL directement.

5.8 JKU / X5U header injection

Headers jku (JWK Set URL) et x5u (X.509 URL) permettent au JWT de spécifier où trouver la clé de vérification. Bibliothèques naïves suivent l'URL, permettant à l'attaquant de pointer vers son propre JWKS.

Mitigation : ignorer ces headers côté serveur. Utiliser une source de vérité connue (JWKS URL configurée).

5.9 Algorithm confusion sur les courbes

Similaire à HMAC/RSA mais avec les variantes ECDSA : ES256 vs ES384, ou ES256 vs ES256K (Bitcoin curve) qui partagent des structures similaires avec vulnérabilités historiques.

Mitigation : algorithmes fixés strictement.

5.10 Replay de refresh tokens volés

Un refresh token volé peut être utilisé à vie si :

  • Le refresh token n'expire pas ou expire trop tard.
  • Pas de rotation : le refresh token reste valide même après émission d'un nouveau.
  • Pas de détection d'anomalie (même refresh token utilisé depuis deux IPs différentes).

Mitigation :

  • Refresh token rotation : chaque usage d'un refresh token émet un nouveau refresh token, l'ancien devient invalide.
  • Detection de replay : si un refresh token déjà rotaté est réutilisé, révoquer toute la chaîne (compromission détectée).
  • Lifetime borné : rotation max 90 jours, après quoi re-authentification forcée.

5.11 Pas de révocation

Un JWT est auto-contenu : sa validité ne dépend que de sa signature et de ses claims. Pas de DB consultée à la validation. Conséquence : impossible de révoquer avant expiration.

Conséquence pratique : un JWT volé reste utilisable jusqu'à expiration. Si exp = 24h, compromission exploitable 24h.

Mitigations :

  • Access tokens courts : 15 minutes max. Compromission limitée.
  • Blacklist de jti pour révocation d'urgence : serveur consulte une liste de jti bannis à la validation. Requête DB/cache, perd le bénéfice "stateless" mais indispensable pour scénarios critiques.
  • Session ID en JWT : le JWT contient un session_id, le serveur vérifie que la session est active en DB. Hybride entre JWT et session classique.

5.12 Stockage côté client mal choisi

Où stocker le JWT côté client ?

EmplacementRisques
localStorageAccessible par tout JS de la page → XSS vole le token
sessionStorageIdem localStorage, persistance plus courte
Cookie sans HttpOnlyAccessible par JS → XSS le vole
Cookie HttpOnly + Secure + SameSiteMeilleur : pas accessible par JS, protégé en transit, protégé CSRF
Variable mémoire JSOK si durée très courte et pas de reload
IndexedDBAccessible par JS → XSS
Mobile Keychain / KeystoreSécurisé sur mobile natif

Pour une web app : cookie HttpOnly Secure SameSite=Strict est le meilleur compromis. Le cookie protège contre XSS. Combiné avec CSRF tokens ou SameSite, protège contre CSRF.

localStorage est une mauvaise pratique sécurité malgré sa popularité. Toute XSS vole le token.

6. Bonnes pratiques 2026

6.1 Access token courte durée + refresh token

  • Access token : 15 minutes.
  • Refresh token : quelques jours (7 à 30), avec rotation.
  • Re-authentification forcée après 90 jours maximum.

6.2 Algorithmes fixés côté serveur

algorithms=["RS256"]  # ou ["EdDSA"] pour nouveaux projets

Jamais d'algorithme lu du header pour choisir la vérification.

6.3 Claims minimal dans le payload

Ne pas stocker en clair :

  • Mots de passe, hashes.
  • Numéros de carte, SSN.
  • Données personnelles sensibles.
  • Secrets ou tokens tiers.

Le JWT est lisible par tout le monde.

6.4 JWKS avec rotation

  • Serveur émetteur publie ses clés publiques dans un JWKS URL.
  • Chaque clé a un kid unique.
  • Rotation périodique (trimestrielle typiquement).
  • Anciennes clés conservées jusqu'à expiration des derniers tokens signés avec.

Côté validateur : caching du JWKS avec TTL court (quelques minutes), re-fetch à la rotation détectée via changement de kid.

6.5 Validation stricte de tous les claims obligatoires

exp, iss, aud, sub = strict par défaut. nbf si utilisé.

6.6 Détection d'anomalies

  • Même JWT utilisé depuis deux IPs géographiquement éloignées en quelques minutes.
  • Volume d'API anormalement élevé sur un token.
  • Refresh token réutilisé après rotation = alarme rouge.

6.7 mTLS sur les APIs sensibles

Pour les communications inter-services critiques, combiner JWT + mTLS : le JWT prouve l'identité du sujet, le certificat client prouve l'identité du service appelant.

6.8 Short-lived et rotation pour M2M

Pour OAuth Client Credentials, préférer :

  • Tokens courte durée émis à la demande.
  • Client secret rotaté régulièrement.
  • Ou mieux : mTLS client authentication à la place du client_secret.

6.9 Observabilité

Logs structurés de chaque validation JWT :

  • Succès / échec.
  • Cause d'échec (signature invalide, expiré, audience incorrecte, kid inconnu).
  • iss, sub, jti (pas le token complet).

Export vers SIEM, alertes sur patterns anormaux.

7. Bibliothèques recommandées 2026

LangageBibliothèqueNotes
Pythonpyjwt, python-jose, authlibPyJWT stable, Authlib plus complet
Node.jsjose, jsonwebtokenjose plus moderne et sécurisé
Gogolang-jwt/jwt/v5, lestrrat-go/jwxjwx riche, v5 stable
Javajava-jwt (Auth0), nimbus-jose-jwtNimbus complet
Rubyruby-jwtMaintenu
.NETSystem.IdentityModel.Tokens.Jwt, Microsoft.AspNetCore.Authentication.JwtBearerNatif Microsoft
Rustjsonwebtoken, jwt-simpleDeux options solides

Critères de choix :

  • Maintenu activement (dernière release moins d'un an).
  • CVE historique faible.
  • Support des algorithmes modernes (EdDSA).
  • Validation stricte par défaut.

7.1 Bibliothèques à éviter

  • node-jsonwebtoken < 9.0.0 : nombreuses CVE historiques, privilégier jose pour nouveaux projets.
  • jwt-decode utilisé seul pour valider : ne valide pas la signature malgré son nom trompeur. Décode uniquement la payload. Bug fréquent chez les débutants.

8. Alternatives à JWT

8.1 Opaque tokens + session store

Token = string aléatoire. Serveur stocke les claims en DB/cache (Redis). Validation = lookup du token.

Avantages :

  • Révocation instantanée (suppression du store).
  • Taille petite (token = 64 chars, pas 500-2000 comme JWT).
  • Pas de risque cryptographique lié au choix d'algorithme.
  • Audit facile par DB lookups.

Inconvénients :

  • Lookup DB à chaque requête (mitigable via cache Redis, latence sous la milliseconde).
  • Pas stateless (dépendance infra validateur-store).

Pour APIs à fort trafic et exigences de révocation : opaque tokens restent excellents.

8.2 PASETO (Platform-Agnostic Security Tokens)

Alternative moderne à JWT créée en 2018 par Scott Arciszewski. Principes :

  • Algorithmes versionnés (v1, v2, v3, v4) au lieu de négociables.
  • Pas de choix d'algorithme côté attaquant : un PASETO v4 est toujours Ed25519.
  • Distinction nette entre local (symétrique chiffré) et public (signé).
  • Moins de vulnérabilités possibles par design.

Adoption croissante, bibliothèques dans la plupart des langages.

8.3 Macaroons

Tokens avec caveats (conditions supplémentaires que le détenteur peut ajouter mais pas retirer). Permet délégation fine (ex. "voici mon token, mais restreint à l'endpoint X seulement").

Utilisé par Google pour certains services internes. Adoption limitée dans l'industrie.

8.4 Biscuit tokens

Tokens authorization-focused avec Datalog pour exprimer les permissions. Supportent offline attenuation (on peut réduire les privilèges d'un token sans serveur). Niche mais intéressant.

9. Pentest et tests de sécurité JWT

9.1 Outils manuels

  • jwt.io : décoder et inspecter visuellement un JWT.
  • jwt_tool (ticarpi) : outil offensif complet, teste alg:none, HMAC confusion, kid injection, bruteforce.
  • Burp Suite JWT extension : intercepter et manipuler JWT dans les requêtes.

9.2 Tests automatisés en CI

  • Vérifier que l'API rejette un JWT avec alg: none.
  • Vérifier que l'API rejette un JWT signé avec la clé publique (HMAC confusion).
  • Vérifier que l'API rejette un JWT expiré.
  • Vérifier que l'API rejette un JWT d'un autre issuer.
  • Vérifier que l'API rejette un JWT d'une autre audience.

Ces tests appartiennent à la suite de sécurité automatisée et tournent à chaque PR.

9.3 Scanner offensif

jwt_tool en mode playbook attaque :

python3 jwt_tool.py <JWT> -M pb

Teste automatiquement toutes les vulnérabilités courantes et rapporte celles exploitables.

10. Décisions d'architecture

10.1 Quand utiliser JWT

  • Fédération d'identité OIDC entre IdP et SPs (standard).
  • Access tokens OAuth 2.0 courte durée.
  • M2M entre services de confiance avec rotation (JWT OAuth Client Credentials).
  • Tokens de contexte dans systèmes distribués où stateless est critique.

10.2 Quand privilégier autre chose

  • Sessions web classiques : cookie session + session store Redis = plus simple, plus sûr.
  • Révocation critique (bancaire, médical) : opaque tokens + session store.
  • Durée de vie très longue : jamais en JWT. Token courte durée + refresh.
  • Données sensibles dans le token : JWT = lisible. Chiffrement nécessaire (JWE ou stockage externe).

10.3 Pattern hybride recommandé 2026

Pour une application web moderne :

  • Authentication : IdP externe (Okta, Entra ID, Auth0) émet ID token JWT (OIDC).
  • Session web : cookie HttpOnly qui réfère à une session server-side (Redis).
  • API access : access token JWT courte durée (15 min), obtenu et renouvelé côté serveur.
  • Service-to-service : mTLS + JWT Client Credentials courte durée.
  • Refresh tokens : opaques avec rotation, stockés en DB avec possibilité de révocation.

Combine les avantages JWT (standard, interopérable, stateless) et session classique (révocable, sécurisée).

11. Erreurs fréquentes

JWT stocké en localStorage avec durée 24h = tout le code JS a accès. Une XSS = vol de la session. Préférer cookie HttpOnly avec session ID référençant un store server-side.

11.2 Décoder sans vérifier

const payload = JSON.parse(atob(token.split('.')[1]));
if (payload.role === 'admin') { ... }

Code qui lit le payload sans vérifier la signature. L'attaquant forge n'importe quel payload. Bug fréquent avec jwt-decode.

11.3 Confiance aux claims custom non signés

Si une partie du contexte vient du JWT, tout doit être signé par l'émetteur de confiance. Pas de mix "certaines claims signées, d'autres modifiables".

11.4 Payload trop gros

Un JWT ne devrait pas dépasser 8 KB (limite de certains proxies et cookies). Payload lourd = latence, mauvaise expérience.

11.5 Secret en dur dans le code

Secret HS256 committé dans Git = compromission permanente dès que quelqu'un accède au repo. Mitigation : secret depuis Vault ou variables d'environnement.

11.6 Clock skew non géré

Deux serveurs dont les horloges diffèrent de quelques secondes peuvent rejeter des JWT valides (exp légèrement dépassé). Tolérance typique : 30 secondes dans la plupart des libs. À configurer.

11.7 Logging du token complet

Loguer Authorization: Bearer eyJ... dans les access logs expose les tokens à quiconque accède aux logs (y compris admins cloud, SIEM operators). Mitigation : masking du header Authorization en log pipeline.

12. Verdict et posture Zeroday

Les JWT sont omniprésents et incontournables en 2026, mais leur apparente simplicité cache une surface de vulnérabilités réelle. La majorité des bugs JWT ne sont pas des zero-days exotiques : ce sont des erreurs de configuration ou d'implémentation récurrentes documentées depuis 10 ans.

Pour un développeur : ne jamais implémenter sa propre vérification JWT. Utiliser une bibliothèque mature, la configurer strictement (algorithms fixés, claims validés), tester les cas d'attaque courants. La compétence différenciante est de comprendre ce que la bibliothèque valide et ce qu'elle ne valide pas par défaut.

Pour un AppSec : la revue JWT est une activité à inclure dans tout audit d'API. Checklist simple mais implacable : algorithme fixé, claims tous validés, durée courte, stockage client approprié, révocation possible.

Pour une organisation : l'investissement en JWT security tools (scanners, tests CI, formation) est faible au regard du coût d'un incident. Beaucoup de fuites massives API démarrent par un JWT mal vérifié.

Pour approfondir : OWASP API Security Top 10 expliqué pour voir JWT dans le contexte API2 Broken Authentication, autorisation d'API : les bases pour la couche authorization qui suit la validation JWT, gestion de session sécurisée pour l'alternative session classique, SSO : définition pour le contexte OIDC qui émet les JWT.

Écrit par

Naim Aouaichia

Expert cybersécurité et fondateur de Zeroday Cyber Academy

Expert cybersécurité avec un master spécialisé et un parcours hybride : développement, DevOps, DevSecOps, SOC, GRC. Fondateur de Hash24Security et Zeroday Cyber Academy. Formateur et créateur de contenu technique sur la cybersécurité appliquée, la sécurité des LLM et le DevSecOps.