JSON Web Tokens (JWT): Autenticazione moderna spiegata
Guida completa ai JWT: struttura, firma, validazione, best practices di sicurezza. Implementazioni in Node.js, Python, uso in autenticazione API moderne.
Big JSON Team
• Technical WriterExpert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.
# JSON Web Tokens (JWT): Autenticazione moderna
I JSON Web Tokens (JWT) sono lo standard de facto per l'autenticazione in applicazioni moderne. Questa guida ti insegnerà tutto sui JWT, dalla teoria all'implementazione sicura.
Cos'è un JWT?
Definizione
Un JWT è un token compatto e URL-safe per trasmettere informazioni tra parti come oggetto JSON. È firmato digitalmente, quindi verificabile e affidabile.
Struttura
Un JWT consiste di tre parti separate da punti (.):
header.payload.signature
Esempio reale:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1hcmNvIFJvc3NpIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Le tre parti
1. Header (rosso){
"alg": "HS256",
"typ": "JWT"
}
alg: Algoritmo di firma (HS256, RS256, etc.)typ: Tipo di token (sempre JWT)
{
"sub": "1234567890",
"name": "Marco Rossi",
"email": "marco@example.com",
"iat": 1516239022,
"exp": 1516242622
}
- Claims (dichiarazioni) sull'utente
- Metadata del token
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
- Firma crittografica
- Verifica integrità e autenticità
Come funziona JWT
Flow di autenticazione
1. Client → Server: Login (username + password)
Server verifica credenziali
Server → Client: JWT token
Client salva token (localStorage, cookie)
Client → Server: Richiesta + JWT in header
Server verifica JWT
Server → Client: Risposta autorizzata
Diagramma:
┌─────────┐ ┌─────────┐
│ Client │ │ Server │
└────┬────┘ └────┬────┘
│ │
│ POST /login │
│ {username, password} │
├───────────────────────────────────────>│
│ │
│ │ Verifica
│ │ credenziali
│ │
│ 200 OK │
│ {token: "eyJ..."} │
│<───────────────────────────────────────┤
│ │
│ GET /api/protected │
│ Authorization: Bearer eyJ... │
├───────────────────────────────────────>│
│ │
│ │ Verifica
│ │ JWT
│ │
│ 200 OK │
│ {data: ...} │
│<───────────────────────────────────────┤
Claims standard
Registered Claims
Claims riservati (raccomandati):
iss(Issuer) - Chi ha emesso il tokensub(Subject) - Soggetto (user ID)aud(Audience) - Destinatarioexp(Expiration) - Scadenza (timestamp)nbf(Not Before) - Valido da (timestamp)iat(Issued At) - Emesso il (timestamp)jti(JWT ID) - ID univoco token
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "https://api.example.com",
"exp": 1735228800,
"nbf": 1735142400,
"iat": 1735142400,
"jti": "abc123xyz"
}
Public Claims
Claims personalizzati per la tua app:
{
"sub": "user123",
"name": "Marco Rossi",
"email": "marco@example.com",
"role": "admin",
"permissions": ["read", "write", "delete"],
"company": "TechCorp"
}
Implementazione Node.js
Setup
npm install jsonwebtoken
Generare JWT
const jwt = require('jsonwebtoken');
// Secret key (DEVE essere in .env!)
const SECRET = process.env.JWT_SECRET;
function generateToken(user) {
const payload = {
sub: user.id,
name: user.name,
email: user.email,
role: user.role
};
const options = {
expiresIn: '1h', // Scade in 1 ora
issuer: 'my-app', // Chi emette
audience: 'my-api' // Per chi
};
return jwt.sign(payload, SECRET, options);
}
// Uso in login
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Verifica credenziali
const user = await User.findOne({ username });
if (!user || !await user.comparePassword(password)) {
return res.status(401).json({ error: 'Credenziali non valide' });
}
// Genera token
const token = generateToken(user);
res.json({ token });
});
Verificare JWT
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET, {
issuer: 'my-app',
audience: 'my-api'
});
return { valid: true, payload: decoded };
} catch (error) {
return { valid: false, error: error.message };
}
}
// Middleware autenticazione
function authMiddleware(req, res, next) {
// Estrai token da header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token mancante' });
}
const token = authHeader.substring(7); // Rimuovi "Bearer "
const { valid, payload, error } = verifyToken(token);
if (!valid) {
return res.status(401).json({ error: Token non valido: ${error} });
}
// Aggiungi user a request
req.user = payload;
next();
}
// Usa middleware
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({
message: 'Accesso autorizzato!',
user: req.user
});
});
Refresh tokens
// Genera coppia access + refresh token
function generateTokenPair(user) {
const accessToken = jwt.sign(
{ sub: user.id, role: user.role },
ACCESS_SECRET,
{ expiresIn: '15m' } // Access token breve
);
const refreshToken = jwt.sign(
{ sub: user.id, type: 'refresh' },
REFRESH_SECRET,
{ expiresIn: '7d' } // Refresh token lungo
);
return { accessToken, refreshToken };
}
// Endpoint refresh
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
try {
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
if (decoded.type !== 'refresh') {
throw new Error('Token non valido');
}
// Genera nuovo access token
const user = await User.findById(decoded.sub);
const newAccessToken = jwt.sign(
{ sub: user.id, role: user.role },
ACCESS_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken: newAccessToken });
} catch (error) {
res.status(401).json({ error: 'Refresh fallito' });
}
});
Implementazione Python
Setup
pip install pyjwt
Flask esempio
from flask import Flask, request, jsonify
import jwt
from datetime import datetime, timedelta
from functools import wraps
import os
app = Flask(__name__)
SECRET = os.environ.get('JWT_SECRET')
def generate_token(user_id, role):
"""Genera JWT"""
payload = {
'sub': user_id,
'role': role,
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow()
}
return jwt.encode(payload, SECRET, algorithm='HS256')
def verify_token(token):
"""Verifica JWT"""
try:
payload = jwt.decode(
token,
SECRET,
algorithms=['HS256']
)
return payload
except jwt.ExpiredSignatureError:
raise Exception('Token scaduto')
except jwt.InvalidTokenError:
raise Exception('Token non valido')
# Decorator per proteggere routes
def token_required(f):
@wraps(f)
def decorated(args, kwargs):
token = None
# Estrai token
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
if auth_header.startswith('Bearer '):
token = auth_header[7:]
if not token:
return jsonify({'error': 'Token mancante'}), 401
try:
payload = verify_token(token)
request.user = payload
except Exception as e:
return jsonify({'error': str(e)}), 401
return f(args, kwargs)
return decorated
@app.route('/login', methods=['POST'])
def login():
"""Endpoint login"""
data = request.get_json()
username = data.get('username')
password = data.get('password')
# Verifica credenziali (esempio semplificato)
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({'error': 'Credenziali non valide'}), 401
# Genera token
token = generate_token(user.id, user.role)
return jsonify({'token': token})
@app.route('/api/protected')
@token_required
def protected():
"""Route protetta"""
return jsonify({
'message': 'Accesso autorizzato',
'user_id': request.user['sub'],
'role': request.user['role']
})
Algoritmi di firma
Symmetric (HMAC)
HS256, HS384, HS512
- Stessa chiave per firmare e verificare
- Più semplice
- Server-to-server
// Firma
const token = jwt.sign(payload, SECRET, { algorithm: 'HS256' });
// Verifica (stessa chiave!)
const decoded = jwt.verify(token, SECRET);
Asymmetric (RSA)
RS256, RS384, RS512
- Chiave privata per firmare
- Chiave pubblica per verificare
- Più sicuro per distribuzione
const fs = require('fs');
// Chiavi RSA
const privateKey = fs.readFileSync('private.key');
const publicKey = fs.readFileSync('public.key');
// Firma (chiave privata)
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
// Verifica (chiave pubblica)
const decoded = jwt.verify(token, publicKey);
Genera chiavi RSA:
# Private key
openssl genrsa -out private.key 2048
# Public key
openssl rsa -in private.key -pubout -out public.key
Best Practices sicurezza
1. ❌ Non mettere dati sensibili nel payload
Errato:
const token = jwt.sign({
userId: 123,
password: 'secret123', // ❌ MAI!
creditCard: '1234-5678-...', // ❌ MAI!
ssn: '123-45-6789' // ❌ MAI!
}, SECRET);
Corretto:
const token = jwt.sign({
sub: 123,
role: 'user',
permissions: ['read']
// Solo dati non sensibili, pubblici
}, SECRET);
Motivo: JWT payload è solo base64-encoded, NON criptato. Chiunque può decodificare e leggere.
2. ✅ Usa secret forte
// ❌ Debole
const SECRET = 'secret';
// ✅ Forte (almeno 256 bit)
const SECRET = crypto.randomBytes(64).toString('hex');
// 'a7f3c8e2b9d1f6e4c3a8b7d2f1e9c4a6b3d8e7f2c9a5b1d6e3f8c2a7b4d9e1f6...'
Genera e salva:
# Genera random secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Salva in .env
echo "JWT_SECRET=<generated-secret>" >> .env
3. ✅ Imposta expiration breve
// ❌ Troppo lungo
jwt.sign(payload, SECRET, { expiresIn: '30d' });
// ✅ Breve, usa refresh token
jwt.sign(payload, SECRET, { expiresIn: '15m' });
4. ✅ Verifica sempre expiration
try {
const decoded = jwt.verify(token, SECRET);
// Double-check exp
if (decoded.exp < Date.now() / 1000) {
throw new Error('Token scaduto');
}
} catch (error) {
// Handle error
}
5. ✅ Usa HTTPS
JWT deve viaggiare SOLO su HTTPS:
- ❌ HTTP: Token intercettabile
- ✅ HTTPS: Criptato in transito
6. ✅ Implementa token blacklist
Per logout forzato:
// Redis per blacklist
const redis = require('redis');
const client = redis.createClient();
async function blacklistToken(token, exp) {
const ttl = exp - Math.floor(Date.now() / 1000);
await client.setex(blacklist:${token}, ttl, 'true');
}
async function isBlacklisted(token) {
const result = await client.get(blacklist:${token});
return result !== null;
}
// Nel middleware
if (await isBlacklisted(token)) {
return res.status(401).json({ error: 'Token revocato' });
}
7. ✅ Valida issuer e audience
jwt.verify(token, SECRET, {
issuer: 'https://auth.myapp.com',
audience: 'https://api.myapp.com'
});
Storage del token
LocalStorage (browser)
// Salva
localStorage.setItem('token', token);
// Recupera
const token = localStorage.getItem('token');
// Rimuovi (logout)
localStorage.removeItem('token');
// Usa in fetch
fetch('/api/data', {
headers: {
'Authorization': Bearer ${token}
}
});
Pros:
- ✅ Semplice
- ✅ Persiste tra sessioni
Cons:
- ❌ Vulnerabile a XSS
- ❌ Accessibile da JavaScript
HttpOnly Cookies
// Server: Imposta cookie
res.cookie('token', token, {
httpOnly: true, // Non accessibile da JS
secure: true, // Solo HTTPS
sameSite: 'strict',// CSRF protection
maxAge: 3600000 // 1 ora
});
// Browser: Cookie inviato automaticamente
fetch('/api/data', {
credentials: 'include'
});
Pros:
- ✅ Protetto da XSS
- ✅ Automatic sending
Cons:
- ⚠️ Richiede CSRF protection
- ⚠️ Non accessibile da JS
Raccomandazione: Usa HttpOnly cookies per produzione!
Debugging JWT
Online decoder
jwt.io - Paste token e vedi payload
Command line
# Decodifica header
echo "eyJhbGc..." | base64 -d
# Decodifica payload
echo "eyJzdWI..." | base64 -d
# Con jq per formattare
echo "eyJzdWI..." | base64 -d | jq
JavaScript
function decodeJWT(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Token malformato');
}
const header = JSON.parse(Buffer.from(parts[0], 'base64'));
const payload = JSON.parse(Buffer.from(parts[1], 'base64'));
return { header, payload };
}
const { header, payload } = decodeJWT(token);
console.log('Header:', header);
console.log('Payload:', payload);
Conclusione
JWT Best Practices:
- ✅ Usa secret forte (>256 bit)
- ✅ Expiration breve (15min) + refresh token
- ✅ HTTPS obbligatorio
- ✅ HttpOnly cookies per storage
- ✅ Valida issuer/audience
- ✅ Implementa blacklist per revoca
- ❌ MAI dati sensibili nel payload
- ❌ MAI secret in codice
Quando usare JWT:
- ✅ API stateless
- ✅ Microservices
- ✅ Mobile apps
- ✅ Single sign-on (SSO)
Quando NON usare JWT:**
- ❌ Session con stato complesso
- ❌ Frequent revocation necessaria
- ❌ Dati sensibili da trasmettere
JWT è potente ma richiede implementazione attenta per sicurezza!
Articoli Correlati
Python e JSON: Guida completa alla manipolazione dati
Impara a lavorare con JSON in Python: parsing, serializzazione, validazione, file I/O e best practices. Include esempi pratici con json, jsonschema e pydantic.
JavaScript e JSON: Guida completa a JSON.parse() e JSON.stringify()
Guida completa su JSON in JavaScript: parsing, serializzazione, gestione errori, localStorage, fetch API e best practices. Include esempi pratici e troubleshooting.
API JSON e servizi REST: Guida completa per sviluppatori
Guida completa alle API JSON e REST: metodi HTTP, autenticazione, best practices, esempi pratici con fetch e axios. Impara a creare e consumare API moderne.