← Torna al Blog

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 Team16 min di letturasecurity
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

16 min di lettura

# 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)

2. Payload (verde)
{

"sub": "1234567890",

"name": "Marco Rossi",

"email": "marco@example.com",

"iat": 1516239022,

"exp": 1516242622

}

  • Claims (dichiarazioni) sull'utente
  • Metadata del token

3. Signature (blu)
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 token
    • sub (Subject) - Soggetto (user ID)
    • aud (Audience) - Destinatario
    • exp (Expiration) - Scadenza (timestamp)
    • nbf (Not Before) - Valido da (timestamp)
    • iat (Issued At) - Emesso il (timestamp)
    • jti (JWT ID) - ID univoco token

    Esempio:
    {
    

    "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!

    Share:

    Articoli Correlati

    Read in English