Meilleures pratiques de sécurité JWT pour 2026
· Cosyslabs
Les failles de sécurité JWT se trouvent systématiquement parmi les principales vulnérabilités des API. Utilisez les algorithmes RS256 ou ES256, rejetez explicitement l'algorithme none, définissez l'expiration du token d'accès à moins de 15 minutes, implémentez la rotation des tokens de rafraîchissement et vérifiez toujours les signatures côté serveur avant de faire confiance à un claim.
Qu'est-ce qu'un JWT ?
Un JSON Web Token est composé de trois segments encodés en Base64URL séparés par des points :
en-tête.payload.signature
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature
- En-tête : algorithme et type de token
- Payload : claims (données utilisateur, expiration, etc.)
- Signature : preuve cryptographique que le token n'a pas été falsifié
La signature est ce que vous devez vérifier. Un JWT sans signature vérifiée n'est que du JSON non vérifié.
Vulnérabilité critique : Confusion d'algorithme
L'attaque par l'algorithme none
Les premières bibliothèques JWT acceptaient alg: "none" dans l'en-tête, ce qui signifie qu'aucune signature n'était requise. Un attaquant pouvait forger n'importe quel payload :
{
"alg": "none",
"typ": "JWT"
}
Sans vérification de signature, il pouvait prétendre être n'importe quel utilisateur. Correction :
// Node.js — spécifiez toujours explicitement les algorithmes autorisés
jwt.verify(token, clefPublique, { algorithms: ["RS256"] });
// Ne jamais autoriser "none"
// Ne jamais utiliser algorithms: ["RS256", "none"] — c'est une vulnérabilité
Attaque par confusion RS256 vs HS256
HS256 (HMAC) utilise un secret partagé — la même clé signe et vérifie. RS256 (RSA) utilise une paire de clés — la clé privée signe, la clé publique vérifie.
L'attaque : si une bibliothèque voit alg: "HS256" et utilise la clé publique RS256 comme secret HMAC, un attaquant qui a obtenu la clé publique (qui est publique !) peut forger des tokens.
Fixez toujours l'algorithme côté serveur. Ne lisez jamais alg depuis le token pour décider comment le vérifier.
// Vulnérable — lit alg depuis le token
function vérifier(token, clef) {
const { alg } = décoderEnTête(token);
return vérifierAvec(token, clef, alg); // NE JAMAIS faire ça
}
// Sécurisé — l'algorithme est codé en dur côté serveur
function vérifier(token) {
return jwt.verify(token, CLEF_PUBLIQUE, { algorithms: ["RS256"] });
}
Recommandations d'algorithmes
| Algorithme | Type | Cas d'usage |
|---|---|---|
| ES256 | Asymétrique (ECDSA) | Meilleur pour les nouveaux projets — petites signatures, rapide |
| RS256 | Asymétrique (RSA) | Largement supporté, bon pour l'interopérabilité |
| HS256 | Symétrique (HMAC) | Uniquement quand le secret est vraiment partagé et jamais exposé |
| Aucun | Ne jamais utiliser |
Pour les API publiques où plusieurs services vérifient les tokens, utilisez des algorithmes asymétriques (ES256/RS256). La clé privée reste sur le serveur d'authentification ; tous les autres services ne détiennent que la clé publique.
Expiration des tokens et stratégie de rafraîchissement
Les tokens d'accès de courte durée limitent les dégâts en cas de vol. Utilisez un schéma à deux tokens :
- Token d'accès : expire en 5–15 minutes, envoyé avec chaque requête API
- Token de rafraîchissement : expire en 7–30 jours, stocké de manière sécurisée, utilisé uniquement pour obtenir de nouveaux tokens d'accès
// Émettre des tokens
const tokenAccès = jwt.sign(
{ sub: utilisateur.id, role: utilisateur.rôle },
CLEF_PRIVÉE,
{ algorithm: "ES256", expiresIn: "15m" }
);
const tokenRafraîchissement = jwt.sign(
{ sub: utilisateur.id, jti: crypto.randomUUID() },
SECRET_RAFRAÎCHISSEMENT,
{ expiresIn: "7d" }
);
Rotation des tokens de rafraîchissement
Chaque fois qu'un token de rafraîchissement est utilisé, invalidez-le et émettez-en un nouveau. Si un token de rafraîchissement volé est détecté comme utilisé deux fois, invalidez toute la session :
async function rafraîchirTokens(ancienTokenRafraîchissement) {
const payload = jwt.verify(ancienTokenRafraîchissement, SECRET_RAFRAÎCHISSEMENT);
// Vérifier que le token n'a pas été utilisé avant (détection de réutilisation)
const enregistrementToken = await db.tokensRafraîchissement.findOne({ jti: payload.jti });
if (!enregistrementToken || enregistrementToken.utilisé) {
// Réutilisation de token détectée — révoquer toute la famille
await db.tokensRafraîchissement.révoquerFamille(payload.sub);
throw new Error("Réutilisation de token détectée");
}
// Marquer comme utilisé
await db.tokensRafraîchissement.marquerUtilisé(payload.jti);
// Émettre une nouvelle paire
return émettrePaireDeTokens(payload.sub);
}
Stockage sécurisé des JWT
| Stockage | Risque XSS | Risque CSRF | Recommandation |
|---|---|---|---|
localStorage | Élevé | Aucun | Jamais pour les tokens d'authentification |
sessionStorage | Élevé | Aucun | Jamais pour les tokens d'authentification |
| Mémoire (var JS) | Faible | Aucun | Bon pour les tokens d'accès |
Cookie HttpOnly | Aucun | Moyen | Meilleur pour les tokens de rafraîchissement + CSRF |
Stockez les tokens d'accès en mémoire JavaScript. Stockez les tokens de rafraîchissement dans des cookies HttpOnly, Secure, SameSite=Strict.
// Définir le token de rafraîchissement comme cookie HttpOnly
res.cookie("refresh_token", tokenRafraîchissement, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 jours
path: "/auth/refresh", // envoyé uniquement au endpoint de rafraîchissement
});
Claims à toujours valider
Au-delà de la vérification de la signature, validez ces claims standards :
jwt.verify(token, CLEF_PUBLIQUE, {
algorithms: ["ES256"],
issuer: "https://auth.votredomaine.com", // claim iss
audience: "https://api.votredomaine.com", // claim aud
// exp est vérifié automatiquement
// nbf est vérifié automatiquement
});
| Claim | Signification | Toujours valider |
|---|---|---|
exp | Délai d'expiration | Oui |
nbf | Pas avant | Oui |
iss | Émetteur | Oui |
aud | Audience | Oui |
sub | Sujet (ID utilisateur) | Oui, correspondre à la session |
jti | ID JWT | Oui, pour la révocation |
Révocation de JWT
Les JWT sont sans état — un token valide reste valide jusqu'à expiration. Pour une révocation immédiate (déconnexion, changement de mot de passe, suspension de compte), maintenez une liste de blocage :
// À la déconnexion
await redis.setex(`révoqué:${payload.jti}`, secondesTtlToken, "1");
// À chaque requête
async function vérifierToken(token) {
const payload = jwt.verify(token, CLEF_PUBLIQUE, { algorithms: ["ES256"] });
const estRévoqué = await redis.exists(`révoqué:${payload.jti}`);
if (estRévoqué) throw new Error("Token révoqué");
return payload;
}
La courte expiration du token d'accès réduit la durée pendant laquelle vous devez maintenir la liste de blocage.
Débogage des JWT
Utilisez l'Outil Décodeur JWT pour inspecter les en-têtes et les payloads sans envoyer des tokens à des services externes. Tout le décodage se passe dans votre navigateur.
Liste de contrôle
- Utiliser ES256 ou RS256 — jamais HS256 pour les API publiques
- Rejeter explicitement
alg: "none"dans la configuration de la bibliothèque - Fixer l'algorithme côté serveur — ne jamais le lire depuis l'en-tête du token
- Définir l'expiration du token d'accès à 5–15 minutes
- Implémenter la rotation des tokens de rafraîchissement avec détection de réutilisation
- Stocker les tokens de rafraîchissement dans des cookies HttpOnly, les tokens d'accès en mémoire
- Valider
iss,aud,exp,nbfà chaque requête - Implémenter la révocation basée sur JTI pour la déconnexion/changement de mot de passe
- Ne jamais journaliser des JWT complets — ce sont des identifiants porteurs
Plus d'outils de Cosyslabs
- Rough Estimator — Estimez l'effort de développement pour implémenter l'authentification JWT, la rotation des tokens de rafraîchissement et des couches API sécurisées.
- Routine Toolkit — Utilitaires du quotidien incluant une calculatrice de prêt, une calculatrice de dates et un compteur de mots.
- Cosyslabs — Le studio derrière Dev Tools !, PDF Convert All, Unit Convert All, et plus.