DevToolBoxGRATIS
Blog

Sicurezza JWT: Scelta dell'Algoritmo, Scadenza, Rotazione e Prevenzione degli Attacchi

14 mindi DevToolBox

Best Practice di Sicurezza JWT

I JSON Web Token sono ovunque — ma la maggior parte delle implementazioni ha gravi falle di sicurezza. Questa guida copre la scelta degli algoritmi, la scadenza e rotazione dei token e la difesa dagli attacchi JWT più comuni.

Cos'è un JWT e come funziona?

Un JWT è una stringa codificata in Base64URL con tre parti separate da punti: header (algoritmo + tipo), payload (claims) e firma.

Scelta degli algoritmi: HS256 vs RS256 vs ES256

L'errore di sicurezza JWT più comune è usare HS256 in un sistema distribuito. RS256 ed ES256 usano chiavi asimmetriche.

// 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',
  });
}

Strategia di scadenza e rotazione dei token

Access token a breve durata (15 minuti) abbinati a refresh token a lunga durata (7–30 giorni) è lo standard del settore.

// 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 });
});

Attacchi JWT comuni e difese

Le vulnerabilità JWT più pericolose sono: confusione di algoritmo, confusione RS256→HS256, segreti deboli e validazione exp mancante.

// 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();
});

Archiviazione sicura dei token

Access token: conservare in memoria. Refresh token: conservare in cookie httpOnly, Secure, SameSite=Strict.

Domande frequenti

Devo usare JWT o token di sessione opachi?

Usa token di sessione opachi per app renderizzate lato server. Usa JWT per microservizi o API dove è necessaria la verifica stateless.

Come posso revocare un JWT prima della scadenza?

Soluzioni: scadenza breve (15 min), blacklist di token in Redis o versione del token nel record utente.

Quali claim deve contenere ogni JWT?

Obbligatori: iss, sub, aud, exp, iat, jti. Valida sempre iss e aud sul servizio ricevente.

Quanto devono essere lunghi i segreti JWT e le chiavi private?

Segreti HS256: minimo 256 bit. RS256: chiave RSA minima 2048 bit. ES256: chiave a curva ellittica 256 bit.

Strumenti correlati

𝕏 Twitterin LinkedIn
È stato utile?

Resta aggiornato

Ricevi consigli dev e nuovi strumenti ogni settimana.

Niente spam. Cancella quando vuoi.

Prova questi strumenti correlati

JWTJWT Decoder→BBase64 Decoder{ }JSON Formatter

Articoli correlati

Autenticazione JWT: Guida completa all'implementazione

Implementa l'autenticazione JWT da zero. Struttura token, access e refresh token, implementazione Node.js, gestione lato client, best practice di sicurezza e middleware Next.js.

Guida Completa agli Header HTTP

Guida completa agli header HTTP: richiesta, risposta e header di sicurezza.

Guida al design REST API: Best practice per il 2026

Progetta REST API robuste: naming risorse, metodi HTTP, paginazione, gestione errori, versionamento e autenticazione.

JWT Decoder Online: Decode, Inspect & Debug JSON Web Tokens (2026 Guide)

Use our free JWT decoder online to instantly inspect JWT headers, payloads, and claims. Covers JWT structure, standard claims, decoding in JavaScript, Python, Go, Java, signing algorithms, security best practices, and common mistakes.