DevToolBoxGRATUIT
Blog

Sécurité JWT: Choix d'algorithme, Expiration, Rotation et Prévention des Attaques

14 minpar DevToolBox

Meilleures pratiques de sécurité JWT : Algorithmes, expiration, rotation et prévention des attaques

Les JSON Web Tokens sont omniprésents — mais la plupart des implémentations présentent des failles de sécurité critiques. Ce guide couvre la sélection des algorithmes, l'expiration et la rotation des tokens, les patterns de refresh token, et comment se défendre contre les principales attaques JWT.

Qu'est-ce qu'un JWT et comment fonctionne-t-il ?

Un JWT est une chaîne encodée en Base64URL avec trois parties séparées par des points : en-tête (algorithme + type), payload (claims) et signature.

Sélection d'algorithmes : HS256 vs RS256 vs ES256

L'erreur de sécurité JWT la plus courante est d'utiliser HS256 dans un système distribué. RS256 et ES256 utilisent des clés asymétriques : seul le serveur d'authentification détient la clé de signature privée.

// Anatomy of a JWT — Header.Payload.Signature

// 1. Header (Base64URL decoded)
const header = {
  alg: "RS256",   // Algorithm: RS256 (asymmetric) preferred over HS256 (symmetric)
  typ: "JWT"
};

// 2. Payload (Base64URL decoded) — the claims
const payload = {
  iss: "https://auth.example.com",         // Issuer
  sub: "user_01HXYZ123",                  // Subject (user ID)
  aud: "https://api.example.com",          // Audience (target service)
  exp: 1735689600,                         // Expiry: Unix timestamp (15 min from now)
  iat: 1735688700,                         // Issued at
  jti: "8f14e0f9-21af-4c1e-8f26-3b4a5c6d7e8f", // Unique token ID (for revocation)
  roles: ["user"],                         // Custom claims
  email: "alice@example.com"
};

// 3. Signature (server-side only)
// RS256: sign(base64url(header) + '.' + base64url(payload), PRIVATE_KEY)
// Verification: verify(token, PUBLIC_KEY) — other services only need the public key

// --- Generating a JWT with jsonwebtoken (Node.js) ---
import jwt from 'jsonwebtoken';
import { readFileSync } from 'fs';
import { randomUUID } from 'crypto';

const privateKey = readFileSync('./private.pem');   // RSA private key
const publicKey  = readFileSync('./public.pem');    // RSA public key (shared with all services)

function issueAccessToken(userId: string, roles: string[]): string {
  return jwt.sign(
    {
      sub: userId,
      aud: 'https://api.example.com',
      roles,
      jti: randomUUID(),
    },
    privateKey,
    {
      algorithm: 'RS256',
      issuer: 'https://auth.example.com',
      expiresIn: '15m',  // Short-lived access token
    }
  );
}

function verifyAccessToken(token: string) {
  return jwt.verify(token, publicKey, {
    algorithms: ['RS256'],           // CRITICAL: whitelist algorithms — never allow 'none'
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
  });
}

Stratégie d'expiration et de rotation des tokens

Les tokens d'accès à courte durée de vie (15 minutes) associés à des tokens de rafraîchissement à longue durée de vie (7 à 30 jours) constituent la norme industrielle.

// Refresh Token Pattern — Stateless Access + Stateful Refresh

import jwt from 'jsonwebtoken';
import { randomBytes } from 'crypto';

// ---- Token issuance ----
async function issueTokenPair(userId: string) {
  const accessToken = jwt.sign(
    { sub: userId, jti: randomUUID() },
    process.env.JWT_PRIVATE_KEY!,
    { algorithm: 'RS256', expiresIn: '15m' }
  );

  // Refresh token: random bytes stored in DB — NOT a JWT
  const refreshToken = randomBytes(64).toString('hex');
  const refreshExpiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

  // Store hashed refresh token in DB (never store plaintext)
  await db.refreshTokens.create({
    tokenHash: sha256(refreshToken),
    userId,
    expiresAt: refreshExpiry,
    createdAt: new Date(),
  });

  return { accessToken, refreshToken, refreshExpiry };
}

// ---- Token refresh endpoint ----
app.post('/auth/refresh', async (req, res) => {
  const { refreshToken } = req.cookies; // httpOnly cookie
  if (!refreshToken) return res.status(401).json({ error: 'No refresh token' });

  const stored = await db.refreshTokens.findOne({
    tokenHash: sha256(refreshToken),
    expiresAt: { >: new Date() }, // Not expired
    revokedAt: null,                  // Not revoked
  });

  if (!stored) return res.status(401).json({ error: 'Invalid or expired refresh token' });

  // Rotate: invalidate old, issue new pair (refresh token rotation)
  await db.refreshTokens.update(
    { id: stored.id },
    { revokedAt: new Date() }         // Revoke old refresh token
  );

  const newPair = await issueTokenPair(stored.userId);

  // Set new refresh token as httpOnly cookie
  res.cookie('refreshToken', newPair.refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    expires: newPair.refreshExpiry,
    path: '/auth/refresh',            // Scope cookie to refresh endpoint only
  });

  return res.json({ accessToken: newPair.accessToken });
});

// ---- Logout: revoke refresh token ----
app.post('/auth/logout', async (req, res) => {
  const { refreshToken } = req.cookies;
  if (refreshToken) {
    await db.refreshTokens.update(
      { tokenHash: sha256(refreshToken) },
      { revokedAt: new Date() }
    );
  }
  res.clearCookie('refreshToken', { path: '/auth/refresh' });
  return res.json({ success: true });
});

Attaques JWT courantes et défenses

Les vulnérabilités JWT les plus dangereuses sont : la confusion d'algorithme, la confusion RS256→HS256, les secrets faibles et la validation de l'exp manquante.

// JWT Attack Defenses — Code Patterns

// ---- ATTACK 1: Algorithm confusion ("none" bypass) ----
// VULNERABLE: never use verify() without specifying allowed algorithms
const VULNERABLE = jwt.verify(token, secret); // attacker can set alg: "none"

// SAFE: always whitelist algorithms
const SAFE = jwt.verify(token, publicKey, {
  algorithms: ['RS256'], // Never include 'none', 'HS256' if you're using RS256
});

// ---- ATTACK 2: RS256 > HS256 confusion ----
// Attacker changes alg from RS256 to HS256 and signs with the PUBLIC key as the secret
// Defense: always specify algorithm on verify — never infer from the token header
// VULNERABLE:
const header = JSON.parse(atob(token.split('.')[0]));
jwt.verify(token, publicKey, { algorithms: [header.alg] }); // attacker controls this!

// SAFE:
jwt.verify(token, publicKey, { algorithms: ['RS256'] }); // hardcoded

// ---- ATTACK 3: Weak HS256 secrets (brute-force) ----
// VULNERABLE: short or predictable secrets
const BAD_SECRET = 'secret';
const BAD_SECRET2 = 'mysupersecretkey';

// SAFE: 256-bit cryptographically random secret
import { randomBytes } from 'crypto';
const GOOD_SECRET = randomBytes(32).toString('hex'); // 256-bit
// Store in environment variable, rotate annually

// ---- ATTACK 4: Missing expiry validation ----
// Some older libraries skip exp check by default
// SAFE: always validate exp (jsonwebtoken does this by default)
try {
  const decoded = jwt.verify(token, publicKey, {
    algorithms: ['RS256'],
    clockTolerance: 30, // Allow 30 seconds clock skew max
    ignoreExpiration: false, // NEVER set this to true in production
  });
} catch (err) {
  if (err instanceof jwt.TokenExpiredError) {
    return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
  }
  return res.status(401).json({ error: 'Invalid token' });
}

// ---- Token blacklist (for early revocation via jti) ----
import { createClient } from 'redis';
const redis = createClient();

async function revokeToken(jti: string, exp: number) {
  const ttl = exp - Math.floor(Date.now() / 1000); // seconds until natural expiry
  if (ttl > 0) {
    await redis.set(`jwt:revoked:${jti}`, '1', { EX: ttl });
  }
}

async function isTokenRevoked(jti: string): Promise<boolean> {
  return (await redis.get(`jwt:revoked:${jti}`)) !== null;
}

// Middleware: check blacklist
app.use(async (req, res, next) => {
  const decoded = jwt.verify(req.headers.authorization?.split(' ')[1] ?? '', publicKey, {
    algorithms: ['RS256'],
  });
  if (await isTokenRevoked((decoded as jwt.JwtPayload).jti ?? '')) {
    return res.status(401).json({ error: 'Token revoked' });
  }
  next();
});

Stockage sécurisé des tokens

Tokens d'accès : stockez en mémoire (variable JavaScript). Tokens de rafraîchissement : stockez dans des cookies httpOnly, Secure, SameSite=Strict.

Questions fréquentes

Devrais-je utiliser JWT ou des tokens de session opaques ?

Utilisez des tokens de session opaques pour les applications rendues côté serveur. Utilisez les JWT pour les microservices ou les API où une vérification sans état est nécessaire.

Comment révoquer un JWT avant son expiration ?

Solutions : expiration courte (15 min), liste noire de tokens dans Redis, ou version de token dans l'enregistrement utilisateur.

Quels claims doit contenir chaque JWT ?

Obligatoires : iss, sub, aud, exp, iat, jti. Validez toujours iss et aud sur le service destinataire.

Quelle longueur pour les secrets JWT et les clés privées ?

Secrets HS256 : minimum 256 bits. RS256 : clé RSA minimum 2048 bits. ES256 : clé de courbe elliptique 256 bits.

Outils associés

𝕏 Twitterin LinkedIn
Cet article vous a-t-il aidé ?

Restez informé

Recevez des astuces dev et les nouveaux outils chaque semaine.

Pas de spam. Désabonnez-vous à tout moment.

Essayez ces outils associés

JWTJWT Decoder→BBase64 Decoder{ }JSON Formatter

Articles connexes

Authentification JWT : Guide d'implementation complet

Implementez l'authentification JWT de zero. Structure des tokens, access et refresh tokens, implementation Node.js, gestion cote client, bonnes pratiques de securite et middleware Next.js.

Guide Complet des En-têtes HTTP

Guide complet des en-têtes HTTP : requête, réponse et en-têtes de sécurité.

Guide de conception d'API REST : Meilleures pratiques 2026

Concevez des API REST robustes : nommage des ressources, méthodes HTTP, pagination, gestion des erreurs, versioning et authentification.

Decodeur JWT en Ligne : Decoder, Inspecter et Debugger les JSON Web Tokens (Guide 2026)

Utilisez notre decodeur JWT en ligne gratuit pour inspecter instantanement les en-tetes, charges utiles et revendications JWT. Couvre la structure JWT, les revendications standard, le decodage en JavaScript/Python/Go/Java, les algorithmes de signature et les bonnes pratiques de securite.