Skip to content
Retour au blog
Sécurité

Bonnes pratiques de sécurité JWT : attaques et défenses

Sécurisez vos JWT : bloquez les attaques alg:none et de confusion d'algorithme, verrouillez les algorithmes, faites tourner les clés et validez les claims.

13 min de lecture

Bonnes pratiques de sécurité JWT : attaques, défenses et checklist 2026

Les JSON Web Tokens font tourner l’essentiel de l’authentification moderne, et pourtant les bonnes pratiques qui les protègent passent à la trappe bien plus souvent qu’on ne l’imagine. Le JWT est devenu le format de credential par défaut pour OAuth 2.0, OpenID Connect et les appels de service à service dans les microservices. C’est aussi la source d’un flux régulier de CVE chaque année, et presque toutes remontent aux mêmes erreurs évitables : accepter des tokens non signés, faire confiance à l’algorithme choisi par un attaquant, utiliser un secret de signature faible, négliger la validation des claims.

Un JWT est sécurisé quand quatre conditions tiennent en même temps. La signature est intacte, l’algorithme ne peut pas être échangé par un attaquant, les claims sont réellement vérifiés, et le token est rangé là où on ne peut pas le voler trivialement. Qu’une seule de ces conditions tombe, et vous obtenez un contournement d’authentification, pas une API durcie. Ce guide couvre les trois attaques qui comptent le plus, puis les défenses : choisir et verrouiller un algorithme, gérer les clés, valider les claims, stocker les tokens. Il finit par une checklist que vous pouvez coller dans une revue de code.

Comment une signature JWT vous protège réellement (et ce qu’elle ne protège pas)

Avant qu’une attaque ait du sens, un fait à poser : un JWT est encodé, pas chiffré. Un token signé comporte trois segments Base64URL reliés par des points, sous la forme header.payload.signature. Le header et le payload ne sont que du Base64URL de JSON. Quiconque détient le token peut lire chaque claim qu’il contient. Collez n’importe quel token dans notre décodeur JWT et vous verrez le header et le payload affichés en JSON lisible, sans aucune clé. Le payload est public par conception.

D’où vient alors la sécurité ? De la signature, et d’elle seule. C’est une valeur cryptographique calculée sur le header et le payload à l’aide d’un secret (HMAC) ou d’une clé privée (RSA, ECDSA). Un attaquant peut lire un token librement, mais sans la clé de signature il ne peut pas produire un token différent qui passe la vérification. Tout le modèle de confiance tient sur cette seule propriété.

Deux conséquences. D’abord, ne mettez jamais de secrets dans le payload (mots de passe, clés d’API, données personnelles complètes), puisqu’il est lisible par quiconque intercepte le token. Ensuite, toute votre posture de sécurité repose sur une seule étape : vérifier correctement la signature. C’est l’étape que les attaquants visent. Pour aller plus loin sur la lecture des tokens segment par segment, voyez comment décoder un JWT.

Les 3 attaques JWT critiques (et comment bloquer chacune)

La plupart des vulnérabilités JWT sont des variations sur un même thème : le serveur fait confiance à quelque chose que l’attaquant contrôle. Voici les trois qui cassent l’authentification pour de bon, avec le mécanisme derrière chacune et le correctif.

1. L’attaque alg:none : contournement par token non signé

La spec JWS inclut une valeur alg égale à none, qui signifie « non signé ». Un token alg:none a un segment de signature vide et se termine quand même par un point final, comme header.payload.. L’attaque est simple : prenez un token valide, passez le alg du header à none, glissez-y les claims de votre choix (disons "role": "admin"), puis supprimez la signature. Les premières bibliothèques JWT acceptaient cela par défaut, et le token forgé traversait alors la vérification sans encombre. Aucune clé, aucune signature, usurpation complète.

Pour voir à quoi ressemble un tel token, chargez l’exemple « alg:none » dans notre décodeur JWT : il fait apparaître un avertissement rouge explicite, le token est non signé et ne doit jamais être accepté pour l’authentification. En reproduire un soi-même prend une minute et aide à saisir la menace.

La défense, c’est une allowlist d’algorithmes explicite à chaque appel de vérification. Ne laissez jamais le réglage par défaut de la bibliothèque décider de ce qui est acceptable : les anciens défauts étaient permissifs, et le coût d’être explicite se résume à une option supplémentaire.

// WRONG — the library may accept alg:none or any algorithm
jwt.verify(token, key);

// RIGHT — pin the exact algorithm you expect
jwt.verify(token, key, { algorithms: ['RS256'] });

none ne doit jamais apparaître dans ce tableau. Si votre bibliothèque ne sait pas verrouiller les algorithmes, remplacez-la.

2. La confusion d’algorithme : RS256 rétrogradé en HS256

C’est la vulnérabilité JWT la plus dangereuse en pratique, connue depuis 2015 et qu’on retrouve encore dans les audits aujourd’hui. Elle exploite les serveurs qui décident comment vérifier d’après le champ alg du header, c’est-à-dire la seule partie du token qu’un attaquant peut réécrire.

Le mécanisme. Votre serveur émet des tokens RS256 : il signe avec une clé privée RSA et vérifie avec la clé publique correspondante. Cette clé publique est, par définition, publique ; elle peut figurer dans votre endpoint JWKS ou dans votre dépôt. L’attaquant la récupère, passe le header du token de RS256 à HS256, et signe un payload forgé avec HMAC-SHA256 en se servant de la chaîne de la clé publique comme secret HMAC. Côté vérification : si votre code lit alg dans le header et choisit HMAC en conséquence, il calcule HMAC-SHA256 sur le token avec cette même clé publique comme secret. Les signatures correspondent. Le token forgé est accepté.

La cause racine, c’est la collision de deux faits : le vérificateur a fait confiance au header alg contrôlé par l’attaquant, et la clé publique RSA était à sa disposition pour servir de clé HMAC. Aucun des deux n’est un bug pris isolément. Une clé publique est censée être publique, et un header alg est censé décrire le token. La vulnérabilité naît à l’instant où votre logique de vérification laisse ce header choisir le type de clé et l’algorithme à utiliser : une valeur écrite par l’attaquant pilote alors le chemin cryptographique exécuté par le serveur.

// WRONG — verification method follows the header's alg field
jwt.verify(token, publicKeyOrSecret);

// RIGHT — hard-code the expected algorithm; never let the header choose
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

Verrouillez explicitement l’algorithme asymétrique (RS256 ou ES256 uniquement), gardez la vérification HMAC sur un chemin de code entièrement séparé de la vérification RSA, et utilisez une bibliothèque maintenue qui distingue les types de clés. Notre décodeur JWT signale tout token de la famille HS avec un avertissement de confusion de clé publique, justement parce que cette attaque est si courante : quand un token que vous attendiez asymétrique se présente en HS256, cet avertissement est votre signal.

3. Secret HMAC faible : attaques par force brute et par dictionnaire

Quand vous utilisez HMAC (HS256/384/512), toute la sécurité du token repose sur l’entropie d’un seul secret. Si ce secret est court, tiré du dictionnaire, ou du genre secret ou password123, un attaquant qui capture un seul token valide peut le casser hors ligne. Des outils comme hashcat enchaînent des milliards de candidats par seconde contre la signature du token. Une fois le secret tombé, l’attaquant forge tous les tokens qu’il veut : des credentials admin valides pour toujours.

Ce qui rend cette attaque sournoise, c’est qu’elle se déroule entièrement hors ligne. L’attaquant ne martèle pas votre endpoint de connexion, donc aucun rate limit ne se déclenche et rien n’apparaît dans vos logs. Il capture un token, casse le secret sur son propre matériel, et ne revient que lorsqu’il peut signer des tokens qui passent toutes vos vérifications. Le correctif n’est pas négociable : utilisez au moins 32 octets aléatoires (256 bits) issus d’une source cryptographiquement sûre, et rangez-le dans un gestionnaire de secrets, jamais dans le code ni dans un dépôt.

// WRONG — guessable, low entropy, crackable in seconds
const secret = "password123";

// RIGHT — 256 bits from a CSPRNG, then load from KMS at runtime
const secret = require('crypto').randomBytes(32).toString('base64');

Besoin vite d’une valeur robuste ? Notre générateur de mot de passe produit des chaînes à haute entropie adaptées à une clé HMAC. Pour mesurer la différence en pratique, signez un token de test avec un secret robuste dans notre encodeur JWT, qui s’exécute entièrement dans le navigateur : le secret ne quitte jamais votre machine. Et lorsque la vérification franchit une frontière de confiance (plusieurs services, vérificateurs tiers), abandonnez complètement HS256 au profit d’un algorithme asymétrique, qu’on aborde juste après.

Choisir et verrouiller le bon algorithme

Le choix de l’algorithme, c’est là que l’attaque par confusion se gagne ou se perd, alors choisissez en connaissance de cause. Les trois que vous utiliserez réellement :

AlgorithmeTypeClé de signature / vérificationQuand l’utiliser
HS256Symétrique (HMAC)Un secret partagéFrontière de confiance unique, la même partie signe et vérifie
RS256Asymétrique (RSA)La clé privée signe / la clé publique vérifieInter-services, vérification par un tiers, rotation JWKS
ES256Asymétrique (ECDSA)La clé privée signe / la clé publique vérifieComme RS256, mais clés plus petites et plus rapides ; à privilégier pour les nouveaux systèmes

La règle est courte. Si la même partie signe et vérifie à l’intérieur d’une seule frontière de confiance, HS256 convient et il est rapide. Si quelqu’un d’autre que le signataire doit vérifier (un autre service, un partenaire, un client public), utilisez un algorithme asymétrique, et préférez ES256 : ses clés et ses signatures sont bien plus petites que celles de RSA à force équivalente. Vous pouvez signer côte à côte des tokens d’exemple HS256, RS256 et ES256 dans l’encodeur JWT pour comparer leur structure et la longueur de leur signature.

Quel que soit votre choix, la défense qui compte reste la même : verrouillez un ensemble d’algorithmes explicite sur l’appel de vérification, et ne faites jamais confiance au champ alg du header. Tout le reste repose sur cette allowlist.

Gestion et rotation des clés

Les algorithmes ne sont jamais plus sûrs que les clés qui les soutiennent, et c’est sur la gestion des clés que la plupart des guides restent muets. Pour HS256, le secret fait au moins 32 octets aléatoires et réside dans un gestionnaire de secrets : AWS Secrets Manager, HashiCorp Vault ou Azure Key Vault. Pour les algorithmes asymétriques, la clé privée a sa place dans un HSM ou un KMS et ne touche jamais le code applicatif ; la clé publique est publiée, en général via un endpoint JWKS que les vérificateurs interrogent.

La rotation doit relever de la routine, pas de l’urgence. Étiquetez chaque clé avec un kid (key ID) dans le header du JWT pour que les vérificateurs sachent quelle clé a signé un token donné. Conservez un petit jeu de clés valides côté vérification, la clé actuelle plus la précédente récente, pour que les tokens signés juste avant une rotation se vérifient encore pendant leur durée de vie. C’est ce chevauchement qui rend la rotation transparente au lieu d’une panne.

Une courte checklist pour les clés :

  • Faites tourner les clés de signature au moins tous les 90 jours, et immédiatement au moindre soupçon de compromission.
  • Publiez les clés publiques via JWKS ; versionnez-les avec kid.
  • Gardez les clés privées et les secrets HMAC dans un KMS ou un HSM : jamais dans git, jamais dans le code client, jamais en dur.
  • En cas de fuite, faites tourner la clé et révoquez les refresh tokens en circulation d’un seul coup.

La validation des claims qu’on ne peut pas sauter

Vérifier la signature prouve qu’un token est authentique. Cela ne prouve pas qu’il est pour vous, maintenant. C’est le rôle de la validation des claims, et c’est la défense la moins coûteuse que vous puissiez ajouter. Cinq claims demandent une vérification à chaque requête :

  • exp (expiration) : rejetez les tokens dont l’expiration est dans le passé.
  • nbf (not before) : rejetez les tokens utilisés avant l’ouverture de leur fenêtre de validité.
  • iat (issued at) : rejetez éventuellement les tokens anormalement anciens.
  • iss (issuer) : confirmez que le token provient de l’émetteur en qui vous avez confiance.
  • aud (audience) : confirmez que le token a été forgé pour votre service. Une vérification aud manquante est le trou silencieux le plus fréquent : elle laisse un token émis pour une API être rejoué contre une autre.

La plupart des bibliothèques valident ces claims pour vous quand vous leur passez les valeurs attendues :

jwt.verify(token, key, {
  algorithms: ['ES256'],
  issuer: 'https://auth.example.com',
  audience: 'api.example.com',
  clockTolerance: 5, // seconds, for distributed clock skew
});

Autorisez une petite tolérance d’horloge (cinq secondes est une valeur typique) pour qu’une dérive mineure entre serveurs ne rejette pas des tokens par ailleurs valides. Résistez à la tentation de l’élargir : une tolérance généreuse agrandit la fenêtre pendant laquelle un token expiré fonctionne encore, ce que exp existe précisément pour fermer. Pour vérifier d’un coup d’œil les valeurs exp et iat d’un token, glissez-le dans le décodeur JWT et convertissez les timestamps avec notre convertisseur de timestamp Unix.

Durée de vie des tokens et où stocker les JWT

Les contrôles côté serveur ne sont que la moitié de l’histoire. L’endroit où le client conserve le token décide de la facilité avec laquelle on peut le voler, et c’est au stockage que le XSS et le détournement de session se rejoignent. Le schéma qui tient la route : un access token à courte durée de vie (15 à 60 minutes) associé à un refresh token distinct, plus durable et révocable.

La décision de stockage se résume à un seul compromis :

Emplacement de stockageExposition XSSRisque CSRFRecommandation
localStorageÉlevée : tout JavaScript de la page peut le lireAucunÀ éviter pour les tokens de session
Cookie HttpOnly + Secure + SameSite=StrictFaible : invisible pour JavaScriptNécessite une protection CSRFRecommandé pour les sessions

Un token dans localStorage est lisible par n’importe quel script qui s’exécute sur la page : un seul bug XSS fait fuiter toute la session, et l’attaquant peut la rejouer depuis sa propre machine tant qu’elle vit. Un cookie HttpOnly ne peut pas du tout être lu par JavaScript, ce qui ramène les dégâts d’un XSS à ce qu’un attaquant peut faire à l’intérieur d’une page active : mauvais, mais pas un credential volé qu’il peut emporter. En contrepartie, l’approche cookie vous oblige à ajouter une protection CSRF, puisque les cookies accompagnent automatiquement chaque requête ; SameSite=Strict plus un token CSRF règle la question. Gardez l’access token court pour qu’une fuite ait un faible rayon d’impact, et placez le refresh token dans un cookie HttpOnly, Secure, SameSite. À la déconnexion ou en cas de compromission suspectée, révoquez le refresh token côté serveur et faites tourner la clé de signature. Pour le contexte plus large autour du XSS, du CSRF et des cookies sécurisés, voyez notre guide des bonnes pratiques de sécurité web.

Checklist de sécurité JWT

Parcourez ceci avant de livrer toute authentification basée sur JWT :

  • La vérification verrouille une allowlist d’algorithmes explicite et rejette alg:none.
  • La vérification asymétrique code en dur l’algorithme attendu et ne lit jamais alg dans le header (bloque la confusion).
  • Les secrets HS256 font au moins 32 octets aléatoires, chargés depuis un KMS.
  • Les clés privées résident dans un HSM/KMS ; les clés publiques sont publiées via JWKS et versionnées avec kid.
  • Les clés de signature tournent au moins tous les 90 jours.
  • Chaque requête valide exp, nbf, iat, iss et aud, avec une tolérance d’horloge de 5 secondes ou moins.
  • Les access tokens durent 15 à 60 minutes ; les refresh tokens vivent dans un cookie HttpOnly.
  • Aucun secret dans le payload : il est encodé, pas chiffré.

FAQ

Un JWT est-il sécurisé par défaut ?

Non. La sécurité d’un JWT dépend de la configuration. Vous devez verrouiller l’algorithme, rejeter alg:none, utiliser un secret ou une clé à haute entropie, et valider les claims. Les configurations de bibliothèque par défaut ou permissives autorisent fréquemment des contournements d’authentification.

Quelle est la vulnérabilité JWT la plus dangereuse ?

La confusion d’algorithme, où RS256 est rétrogradé en HS256 et où la clé publique sert de secret HMAC. Elle est connue depuis 2015 et apparaît pourtant encore dans les audits, car elle exploite les serveurs qui choisissent la méthode de vérification d’après le alg du header.

Dois-je utiliser HS256 ou RS256 ?

Utilisez HS256 quand la même partie signe et vérifie à l’intérieur d’une seule frontière de confiance. Utilisez RS256 ou ES256 quand un autre service ou un tiers doit vérifier, ou quand vous avez besoin de la rotation JWKS. Pour les nouveaux systèmes, préférez ES256 : des clés plus petites et plus rapides à force égale.

Où dois-je stocker un JWT ?

Préférez un cookie HttpOnly, Secure, SameSite pour les tokens de session, puisque JavaScript ne peut pas le lire et qu’un seul bug XSS ne peut pas le voler. Évitez localStorage pour les tokens de session : n’importe quel XSS fait fuiter toute la session, prête à être rejouée.

À quelle fréquence dois-je faire tourner les clés de signature JWT ?

Faites une rotation au moins tous les 90 jours en routine, et immédiatement au moindre soupçon de compromission. Versionnez les clés avec kid et conservez à la fois la clé active et la précédente récente sur le vérificateur, pour que les tokens signés juste avant une rotation se valident encore.

Un JWT peut-il être falsifié ?

Pas sans la clé de signature : aucun attaquant ne peut forger un token qui passe la vérification. Mais si votre serveur accepte alg:none, est vulnérable à la confusion d’algorithme, ou utilise un secret faible, la signature peut être contournée. Ce sont des défaillances de configuration, pas des failles du JWT lui-même.

Quels claims dois-je valider ?

Validez exp (expiration), nbf (not before), iat (issued at), iss (issuer) et aud (audience). Une vérification aud manquante est la vulnérabilité silencieuse la plus fréquente : elle laisse un token destiné à un service être rejoué contre un autre.

Conclusion

La sécurité JWT n’est pas compliquée, mais chaque couche doit tenir. La signature est votre seule garantie, alors vérifiez-la correctement. Verrouillez un seul algorithme explicite et ne faites jamais confiance au alg du header. Utilisez des clés robustes, en rotation, conservées dans un KMS. Validez exp, nbf, iat, iss et aud à chaque requête. Et stockez les tokens là où le XSS ne peut pas les atteindre.

Pour passer à la pratique, collez n’importe quel token dans notre décodeur JWT afin d’inspecter son algorithme et ses claims et de repérer les risques d’alg:none ou de confusion HS, puis utilisez l’encodeur JWT pour expérimenter la signature entièrement dans votre navigateur : vos clés ne quittent jamais votre appareil.

Tags: jwt security authentication oauth api-security

Articles connexes

Voir tous les articles