Token JWT sicuri in Next.js – guida pratica per ingegneri

Scopri come costruire in Next.js e Node.js un solido sistema JWT, proteggendo i token dal furto, CSRF e XSS.

Bezpieczne tokeny JWT w Next.js – praktyczny przewodnik dla inżynierów

Le moderne applicazioni web si basano sempre più su token JWT (JSON Web Token) come meccanismo di autenticazione principale. Nell'ambiente Next.js, che combina il rendering lato server (SSR) e lato client (CSR), una corretta gestione della sessione richiede un'architettura ben pensata. Questo articolo presenta il modello di minaccia, descrive il funzionamento del meccanismo, mostra il contrasto tra codice vulnerabile e corretto e fornisce una checklist di mitigazione conforme agli standard OWASP, CWE e al principio del least‑privilege.

Modello di minaccia – cosa può andare storto?

I principali vettori di attacco ai sistemi basati su JWT sono:

  • furto del token tramite XSS (uno script iniettato nel browser legge il token dalla memoria locale o da un cookie non protetto);
  • attacco CSRF, quando il browser invia automaticamente il cookie con il token nella richiesta all'API;
  • replay attack – il token intercettato viene riutilizzato prima della scadenza;
  • assenza di rotazione delle chiavi – in caso di perdita è possibile bloccare tutti i token in una sola volta.

Questo modello può essere visualizzato come tre livelli: memorizzazione del token, sua verifica e gestione del ciclo di vita. Ogni livello richiede protezioni specifiche.

Come funziona il meccanismo JWT in Next.js?

Il flusso tipico è il seguente:

  1. L'utente effettua il login, il server (Node.js) genera due token – accessToken (breve durata) e refreshToken (più lungo).
  2. I token vengono inviati al browser in cookie HttpOnly e Secure.
  3. Durante una richiesta API il browser aggiunge automaticamente accessToken. Il middleware di Next.js verifica la firma e la data di scadenza.
  4. Quando accessToken scade, l'applicazione chiama l'endpoint di refresh, fornendo refreshToken. Il server restituisce un nuovo accessToken e, opzionalmente, un nuovo refreshToken.

È fondamentale che i token non siano accessibili da JavaScript (da qui HttpOnly) e che ogni endpoint richieda la verifica della firma usando la chiave corrente.

Esempio: codice vulnerabile vs. codice corretto

Di seguito confrontiamo due snippet – prima un metodo semplice ma pericoloso di memorizzare il token in localStorage, poi l'implementazione consigliata con cookie HttpOnly e middleware in Next.js.

// VULNERABILE – memorizzazione in localStorage (soggetto a XSS)
function login(credentials) {
  fetch('/api/auth/login', {method: 'POST', body: JSON.stringify(credentials)})
    .then(r => r.json())
    .then(data => {
      localStorage.setItem('accessToken', data.accessToken);
      localStorage.setItem('refreshToken', data.refreshToken);
    });
}

// CORRETTO – impostazione di cookie HttpOnly lato server
// pages/api/auth/login.js
import { sign } from 'jsonwebtoken';
export default async function handler(req, res) {
  const { username, password } = req.body;
  // ... verifica delle credenziali ...
  const accessToken = sign({ sub: username }, process.env.JWT_ACCESS_SECRET, { expiresIn: '15m' });
  const refreshToken = sign({ sub: username }, process.env.JWT_REFRESH_SECRET, { expiresIn: '30d' });
  res.setHeader('Set-Cookie', [
    `accessToken=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900`,
    `refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=2592000`
  ]);
  res.status(200).json({ message: 'Logged in' });
}

Nella versione corretta i token non arrivano a JavaScript, e i flag SameSite=Strict riducono il rischio di CSRF. Inoltre, limitiamo il percorso (Path) in modo che i cookie vengano inviati solo agli endpoint dedicati.

Checklist di mitigazione – cosa fare per soddisfare OWASP e CWE

  • Memorizzazione: utilizza cookie HttpOnly + Secure; evita localStorage e sessionStorage per i token.
  • Limitazione di dominio e percorso: imposta Domain e Path affinché il cookie sia disponibile solo per l'API.
  • SameSite: scegli Strict o Lax a seconda delle necessità, per proteggere da CSRF.
  • Validazione: verifica la firma, la data di scadenza e i claim (iss, aud) sul server.
  • Rotazione delle chiavi: cambia regolarmente i segreti JWT; conserva la chiave precedente per un breve periodo per consentire la verifica dei token esistenti.
  • Refresh dei token: utilizza accessToken brevi e refreshToken più lunghi; dopo un refresh riuscito invalida il refresh token precedente.
  • Rate limiting e monitoraggio delle verifiche fallite – riduce il rischio di brute‑force e token‑replay.

L'implementazione dei punti precedenti risponde a CWE‑287 (Improper Authentication) e CWE‑352 (Cross‑Site Request Forgery).

Strategia di rotazione delle chiavi JWT

Le chiavi usate per firmare i token dovrebbero avere una durata definita (ad es. 30 giorni). Il processo di rotazione comprende:

  1. Generare una nuova chiave e salvarla in un archivio sicuro (ad es. AWS KMS, HashiCorp Vault).
  2. Aggiungere la nuova chiave all'elenco delle “accettate” nella configurazione dell'applicazione, mantenendo la chiave precedente come “fallback”.
  3. Dopo il periodo di transizione (ad es. 24 h) rimuovere la chiave vecchia – tutti i token firmati con la chiave più vecchia non saranno più accettati.

In Next.js è possibile realizzarlo con una funzione di supporto:

// utils/jwt.js
import jwt from 'jsonwebtoken';
const keys = {
  current: process.env.JWT_ACCESS_SECRET,
  previous: process.env.JWT_ACCESS_SECRET_OLD,
};
export function verify(token) {
  try {
    return jwt.verify(token, keys.current);
  } catch (e) {
    // fallback to previous key
    return jwt.verify(token, keys.previous);
  }
}

Questo approccio soddisfa il requisito OWASP “Key Management” e minimizza il rischio di interruzione della sessione durante la rotazione.

Errori comuni e compromessi

Even experienced teams make mistakes. I più frequenti sono:

  • Impostare una durata troppo lunga per l'access token – aumenta la superficie di attacco in caso di furto.
  • Mancanza di refresh del refresh token – consente l'uso illimitato di un token rubato.
  • Utilizzare una sola chiave per access e refresh token – rende più difficile la rotazione e aumenta le conseguenze di una fuga.
  • Flag SameSite inappropriati – con None i token sono esposti a CSRF in contesti cross‑site.
  • Esposizione dei token nei log – soprattutto negli ambienti CI/CD.

Soluzioni di compromesso, come il “sliding expiration” per il refresh token, possono aiutare, ma è sempre necessario valutare l'impatto sulla sicurezza e sull'usabilità.

La migliore difesa non è solo la tecnologia, ma la progettazione consapevole delle sessioni fin dall'inizio.

Checklist pratica di implementazione

  • ✅ Usa HttpOnly, Secure e SameSite=Strict quando imposti i cookie con JWT.
  • ✅ Limita il Path dei cookie agli endpoint API.
  • ✅ Implementa middleware in Next.js che verifica il token ad ogni richiesta.
  • ✅ Crea un endpoint di refresh dei token con rotazione del refresh token.
  • ✅ Applica la rotazione delle chiavi ogni 30‑60 giorni, mantenendo la chiave precedente come fallback.
  • ✅ Monitora le verifiche fallite e applica il rate limiting.
  • ✅ Revisiona regolarmente il codice alla ricerca di XSS e rimuovi tutte le inserzioni di dati pericolose.

Seguendo questa lista, il team di ingegneria costruisce una solida base di sicurezza delle sessioni, conforme agli standard OWASP Top 10 e CWE‑798 (Use of Hard‑coded Credentials).

In sintesi, i token JWT sicuri in Next.js richiedono una strategia di archiviazione ponderata, una rotazione regolare delle chiavi e un controllo rigoroso sul ciclo di vita dei token. Implementando le pratiche descritte, ridurrai il rischio di furto di token, attacchi CSRF e XSS, garantendo al contempo la conformità ai migliori standard del settore. Se hai bisogno di supporto per un audit di sicurezza o per l'implementazione di questo sistema nella tua organizzazione, contatta Coderia.it – ti aiuteremo a costruire una soluzione che soddisfi i più alti requisiti di sicurezza e performance.

Cominciamo

Hai un progetto in mente?

Descrivilo in poche righe: rispondo entro 24 ore con un preventivo gratuito e una proposta di stack.