Hai un'app basata su Node.js e devi gestire l'autenticazione senza affidarti a sessioni lato server? Forse hai già sentito parlare di JWT, ma non è chiaro come gestire la scadenza dei token o come proteggere le rotte senza esporre le credenziali. Il problema è concreto: se il token viene intercettato o se non gestisci bene il refresh, l'utente perde la sessione o, peggio, lasci la porta aperta a un attacco. Noi, di Meteora Web, lavoriamo ogni giorno su backend Node.js per clienti che non possono permettersi intoppi di sicurezza. In questa guida ti mostriamo come implementare autenticazione JWT con access e refresh token in Node.js, con codice reale e accorgimenti che abbiamo imparato sul campo.
Che cos'è JWT e perché usarlo per l'autenticazione in Node.js?
JWT (JSON Web Token) è un formato compatto e autosufficiente per trasmettere informazioni in modo sicuro tra parti. A differenza delle sessioni tradizionali (che richiedono un database per mantenere lo stato), JWT contiene tutti i dati necessari dentro il token stesso, firmato digitalmente. Questo lo rende ideale per API REST e architetture stateless.
Perché Node.js e JWT si sposano bene? Node.js è asincrono e gestisce migliaia di connessioni. Con JWT eviti di dover consultare un database a ogni richiesta per verificare la sessione: basta decodificare e verificare la firma. Risparmi latenza e risorse. Ma attenzione: la comodità del "tutto nel token" nasconde delle insidie se non gestisci correttamente la scadenza e la rotazione dei token.
Access token vs refresh token: la coppia vincente
Un singolo token JWT con lunga scadenza (es. 24 ore) è rischioso: se viene rubato, l'attaccante ha accesso per troppo tempo. La soluzione standard è usare due token:
Sponsored Protocol
- Access token — breve durata (es. 15 minuti), contiene l'identità dell'utente e i permessi. Viene inviato in ogni richiesta.
- Refresh token — lunga durata (es. 7 giorni), utilizzato solo per ottenere un nuovo access token. Deve essere conservato in modo sicuro (es. HttpOnly cookie) e può essere revocato.
Questa architettura limita l'esposizione: anche se l'access token viene rubato, è valido poco tempo. Il refresh token non viaggia nelle richieste API normali, quindi è molto più difficile da intercettare.
Come implementare access e refresh token in un progetto Node.js?
Partiamo da un esempio pratico con Express e il pacchetto jsonwebtoken. Per il refresh token useremo un database (es. PostgreSQL o Redis) per memorizzare il token e poterlo invalidare all'occorrenza. Noi consigliamo di non memorizzare mai il refresh token in localStorage: usa un cookie HttpOnly e Secure.
1. Setup del progetto
npm init -y
npm install express jsonwebtoken bcryptjs dotenv
npm install --save-dev @types/jsonwebtoken # se usi TypeScript
Creiamo un file .env per le chiavi segrete:
ACCESS_TOKEN_SECRET=il_tuo_segreto_molto_lungo_e_casuale
REFRESH_TOKEN_SECRET=un_altro_segreto_diverso
ACCESS_TOKEN_EXPIRES=15m
REFRESH_TOKEN_EXPIRES=7d
Mai hardcodare le chiavi. Usa variabili d'ambiente e genera chiavi sicure con openssl rand -base64 32.
Sponsored Protocol
2. Creazione dei token
const jwt = require('jsonwebtoken');
function generateAccessToken(user) {
return jwt.sign(
{ id: user.id, ruolo: user.ruolo },
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: process.env.ACCESS_TOKEN_EXPIRES }
);
}
function generateRefreshToken(user) {
return jwt.sign(
{ id: user.id },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: process.env.REFRESH_TOKEN_EXPIRES }
);
}
Il refresh token contiene solo l'ID utente, non i permessi. Se devi revocarlo, lo salvi nel database in modo da poterlo invalidare.
3. Flusso di login
app.post('/login', async (req, res) => {
// Valida credenziali...
const user = { id: 123, ruolo: 'admin' };
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
// Salva il refresh token (es. in database)
await saveRefreshToken(user.id, refreshToken);
// Invia access token in body, refresh token in cookie sicuro
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true, // solo HTTPS
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
res.json({
accessToken,
expiresIn: 900 // 15 minuti in secondi
});
});
L'access token va inviato nell'header Authorization: Bearer <token> dal client. Il refresh token non deve mai essere accessibile via JavaScript (HttpOnly).
4. Middleware per proteggere le rotte
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token scaduto', code: 'TOKEN_EXPIRED' });
}
return res.sendStatus(403);
}
req.user = user;
next();
});
}
5. Rotta per il refresh
app.post('/refresh', async (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.sendStatus(401);
// Verifica che il token sia ancora valido nel database
const stored = await getRefreshToken(refreshToken);
if (!stored) return res.sendStatus(403);
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
// Genera nuovo access token e nuovo refresh token (rotazione)
const newAccessToken = generateAccessToken({ id: user.id, ruolo: stored.ruolo });
const newRefreshToken = generateRefreshToken({ id: user.id });
// Invalida il vecchio refresh token e salva il nuovo
await revokeRefreshToken(refreshToken);
await saveRefreshToken(user.id, newRefreshToken);
res.cookie('refreshToken', newRefreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
res.json({ accessToken: newAccessToken, expiresIn: 900 });
});
});
La rotazione del refresh token è fondamentale: ogni volta che usi un refresh token, lo sostituisci con uno nuovo. Se un attaccante ruba un refresh token e lo usa, il token legittimo viene invalidato. Se il legittimo proprietario tenta di usare quello vecchio, capisci che c'è stato un furto e puoi revocare tutti i token associati.
Sponsored Protocol
Quali sono le migliori pratiche di sicurezza per JWT in Node.js?
La sicurezza non si ferma alla firma del token. Ecco gli accorgimenti che noi di Meteora Web applichiamo in ogni progetto.
Sponsored Protocol
Non mettere dati sensibili nel payload
JWT è firmato ma non crittografato. Chiunque può decodificarlo (base64). Non inserire password, numeri di carta di credito o dati personali non necessari. Usa solo identificatori e permessi.
Usa chiavi diverse per access e refresh
Se un segreto viene compromesso, l'altro rimane sicuro. Inoltre, per ambienti più esigenti, considera chiavi asimmetriche (RS256) per la firma dei token.
Cookie HttpOnly per il refresh token
Non salvare mai il refresh token in localStorage o sessionStorage: sono vulnerabili a XSS. I cookie con flag HttpOnly, Secure e SameSite sono la scelta più sicura.
Implementa blacklist o revoca
Quando un utente fa logout, invalida il refresh token (cancella dal database). In più, per scenari di furto, tieni una lista nera di token scaduti ma ancora validi (es. in Redis con TTL). Noi lo gestiamo con una tabella refresh_tokens con campo revoked_at.
Limita la validità temporale
Access token: 15 minuti massimo. Refresh token: 7 giorni per la maggior parte delle app. Per app bancarie o healthcare, riduci ulteriormente. Ricorda: più lungo è il refresh token, maggiore è il rischio in caso di furto.
Sponsored Protocol
Proteggi le rotte di refresh da attacchi CSRF
Poiché il refresh token è in un cookie, potrebbe essere inviato automaticamente in una richiesta cross-site. Usa il flag sameSite: 'strict' e, per maggiore sicurezza, un header anti-CSRF (es. X-Requested-By o token CSRF nel corpo).
Log e monitoraggio
Tieni traccia di tutti i tentativi di refresh falliti, specialmente quando un token già revocato viene riutilizzato. Potrebbe indicare un attacco in corso.
Cosa fare adesso
Hai visto come implementare l'autenticazione JWT con access e refresh token in Node.js, con attenzione alla sicurezza. Ora tocca a te:
- Scarica il pacchetto
jsonwebtokene inizia a costruire il tuo sistema di login. - Integra il refresh token in un database (Redis è ideale per le performance).
- Proteggi il refresh token con cookie HttpOnly e Secure e implementa la rotazione.
- Testa lo scenario di furto — simula un attacco XSS e verifica che il refresh token non sia accessibile.
- Controlla i log per rilevare usi anomali dei refresh token.
Se il tuo stack è Node.js e vuoi un backend sicuro senza reinventare la ruota, noi di Meteora Web possiamo aiutarti a progettarlo. Abbiamo costruito piattaforme con autenticazione JWT per clienti di tutta Italia, partendo sempre dai numeri e dalla sicurezza. Per approfondire l'uso di Node.js nel tuo progetto, dai un'occhiata alla nostra guida madre su Node.js per il backend.