Menu

JWT in Node.js: firmare, verificare e proteggere le route

Un JWT è un token firmato fatto di tre parti base64url che il server emette al login e controlla a ogni richiesta. In Node.js lo firmi e lo verifichi con il pacchetto jose o jsonwebtoken e una chiave segreta.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un JWT (JSON Web Token) è una stringa di tre parti base64url, header.payload.signature, che un server dà a un utente al login e controlla a ogni richiesta successiva. La firma dimostra che il token l'ha emesso il server e che nessuno l'ha modificato, ma il payload è solo codificato, non cifrato, quindi chiunque può leggerlo. In Node.js ne crei uno con new SignJWT(payload).sign(secret) dal pacchetto jose e lo controlli con jwtVerify(token, secret).

Stampa 3 parts, poi user-42 admin: jwtVerify ha controllato firma e scadenza e ha restituito il payload. Cambia di un carattere il secret passato a jwtVerify ed eseguilo di nuovo: la verifica fallisce con signature verification failed.

Cosa c'è dentro un JWT

Header e payload sono JSON codificato in base64url. Per decodificarli non serve nessuna chiave, ed è per questo che un JWT non deve mai contenere una password o altro di segreto.

Le prime due righe stampano {"alg":"HS256"} e {"name":"Ada","role":"editor"}, leggibili da chiunque abbia il token. La firma (43 caratteri per HS256) è ciò che impedisce di modificarlo: cambia editor in admin e la firma non corrisponde più.

ParteContieneSegreta?
HeaderL'algoritmo, come HS256No, leggibile
PayloadI claim: sub (id utente), exp (scadenza), iat (data di emissione), i tuoi campiNo, leggibile
SignatureFirma HMAC o RSA delle prime due partiServe la chiave per crearla

Scadenza e verifica fallita

setExpirationTime('15m') scrive un claim exp, e jwtVerify rifiuta un token scaduto. Ogni rifiuto è un errore con un code, così puoi distinguere un token scaduto da uno contraffatto.

Il primo controllo fallisce con ERR_JWT_EXPIRED e il secondo, con la firma modificata, con ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Per la tua API significano la stessa cosa (rispondere 401), ma scrivere il codice nel log aiuta nel debug.

Proteggere una route Express con un Bearer token

Il client fa il login una volta, conserva il token e lo invia nell'header Authorization come Bearer <token>. Un middleware lo verifica e mette il payload in req.user per la route.

La prima richiesta, GET /me senza token, riceve 401 e Missing token. Invia POST /login con il body {"username": "ada", "password": "lovelace"}, copia il token dalla risposta, poi invia di nuovo GET /me con l'header Authorization: Bearer <token>: la risposta è {"id":"user-1","name":"ada"}.

Il pacchetto jsonwebtoken

jsonwebtoken (npm install jsonwebtoken) è l'altra scelta comune. I suoi sign e verify sono sincroni (con callback opzionali) invece di restituire promise:

const jwt = require('jsonwebtoken');

const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
    subject: 'user-42',
    expiresIn: '1h',
});

const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid

Per il codice nuovo usa jose: non ha dipendenze e gira anche negli edge runtime e nei browser. I token di entrambe le librerie sono JWT standard, quindi una può verificare quello che ha firmato l'altra.

Errori comuni

Usare decode invece di verify. jwt.decode e decodeJwt leggono il payload senza controllare la firma, quindi un token contraffatto passa. Fidati solo di quello che restituisce verify.

Un secret corto o finito nel commit. Un secret HS256 indovinabile permette a un attaccante di firmare i suoi token. Usa almeno 32 byte casuali, leggilo da process.env e non metterlo mai in un commit.

Token che non scadono mai. Senza exp, un token trapelato funziona per sempre. Imposta sempre una scadenza.

Domande frequenti

Dove deve salvare il JWT il client?

In un'app nel browser la scelta più sicura è un cookie HttpOnly e Secure, perché gli script della pagina non possono leggerlo. localStorage è più semplice, ma qualsiasi script della pagina, anche uno iniettato, può leggere il token. Le app mobile usano lo storage sicuro della piattaforma.

Come faccio il logout di un utente con i JWT?

Il server non può annullare un token che ha già firmato; resta valido fino alla scadenza. Cancellalo sul client, tieni gli access token di breve durata e, se ti serve un logout immediato, tieni sul server una lista degli id dei token revocati (jti) e controllala a ogni richiesta.

HS256 o RS256?

HS256 usa un unico secret condiviso per firmare e verificare, adatto a un solo backend. RS256 firma con una chiave privata e verifica con una chiave pubblica, così altri servizi possono controllare i token senza poterli creare.

Per quanto tempo deve essere valido un JWT?

Gli access token di solito valgono da 5 a 60 minuti. Per sessioni più lunghe, abbinali a un refresh token conservato con più attenzione, che il client scambia con un nuovo access token.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

Inizia