JWT Decoder Python — Decodificare JWT con PyJWT

·DevOps Engineer & Python Automation Specialist·Revisionato daMaria Santos·Pubblicato

Usa il Decoder JWT gratuito direttamente nel tuo browser — nessuna installazione.

Prova Decoder JWT online →

Ogni API che utilizza l'autenticazione basata su token ti fornisce prima o poi un JWT, e capire cosa contiene è uno di quei compiti che si presenta costantemente durante lo sviluppo. Un decoder JWT in Python prende quella stringa base64 opaca e la trasforma in un dizionario di claim leggibile con cui puoi lavorare concretamente. Il pacchetto PyPI che cerchi è PyJWT — si installa con pip install PyJWT ma si importa come import jwt. Questa guida tratta jwt.decode() con verifica completa della firma, la decodifica senza segreto per ispezioni rapide, la decodifica manuale base64 senza alcuna libreria, la verifica con chiave pubblica RS256, e gli errori comuni che ho incontrato in sistemi di autenticazione in produzione. Per un controllo veloce una tantum, il JWT Decoder online lo fa istantaneamente senza scrivere codice. Tutti gli esempi sono per Python 3.10+ e PyJWT 2.x.

  • pip install PyJWT, poi import jwt — il nome del pacchetto e il nome di import sono diversi, il che mette in difficoltà quasi tutti.
  • jwt.decode(token, key, algorithms=["HS256"]) restituisce un semplice dict con i claim. Passa sempre algorithms in modo esplicito.
  • Per ispezionare i claim senza verifica: jwt.decode(token, options={"verify_signature": False}, algorithms=["HS256"]).
  • Per token RSA/EC: pip install PyJWT cryptography — il backend cryptography è necessario per gli algoritmi asimmetrici.
  • La decodifica manuale (base64 + json) funziona senza alcuna libreria ma salta tutta la validazione della firma e della scadenza.

Cos'è la Decodifica JWT?

Un JSON Web Token è composto da tre segmenti codificati in base64url separati da punti: un header (algoritmo e tipo di token), un payload (i claim — ID utente, ruoli, data di scadenza), e una firma. Decodificare un JWT significa estrarre i segmenti header e payload, decodificarli con base64url e analizzare il JSON risultante in un dizionario di claim.

L'header indica quale algoritmo è stato usato per firmare il token e a volte include un kid (key ID) per trovare la chiave di verifica corretta. Il payload contiene i dati effettivi: a chi è stato emesso il token (sub), quando scade (exp), per quale servizio è destinato (aud), più eventuali claim personalizzati che la tua applicazione definisce. Il segmento firma prova che il token non è stato manomesso, ma hai bisogno della chiave segreta o della chiave pubblica per verificarlo. Decodifica e verifica sono operazioni separate. Puoi decodificare il payload senza verificare la firma (utile per il debug), ma non fidarti mai di claim non verificati per le decisioni di autorizzazione.

Before · json
After · json
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
  "sub": "usr_8f2a",
  "role": "admin",
  "exp": 1711815600
}

jwt.decode() — Decodifica e Verifica con PyJWT

jwt.decode() è la funzione principale della libreria PyJWT. Riceve la stringa del token codificato, la chiave segreta (per algoritmi HMAC) o la chiave pubblica (per RSA/EC), e un elenco algorithms obbligatorio. La funzione verifica la firma, controlla i claim standard come exp e nbf, e restituisce il payload come dizionario Python. Se qualcosa fallisce — firma errata, token scaduto, algoritmo sbagliato — solleva un'eccezione specifica.

Esempio Minimale Funzionante

Python 3.10+
import jwt

# Un segreto condiviso tra l'emittente e questo servizio
SECRET_KEY = "k8s-webhook-signing-secret-2026"

# Codifica prima un token (simulando ciò che emetterebbe un auth server)
token = jwt.encode(
    {"sub": "usr_8f2a", "role": "admin", "team": "platform"},
    SECRET_KEY,
    algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3Jf...

# Decodifica e verifica il token
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin', 'team': 'platform'}
print(payload["role"])
# admin

Il parametro algorithms è un elenco, non una stringa singola, ed è obbligatorio in PyJWT 2.x. Si tratta di una funzionalità di sicurezza: senza di esso, un attaccante potrebbe creare un token con alg: none nell'header e bypassare completamente la verifica. Specifica sempre esattamente quali algoritmi la tua applicazione accetta. Se emetti solo token HS256, l'elenco dovrebbe essere ["HS256"] — non ["HS256", "RS256", "none"]. Tenere l'elenco ristretto riduce la superficie di attacco.

Una cosa che mi ha confuso all'inizio: PyJWT 2.x ha modificato jwt.encode() per restituire una stringa anziché bytes. Se leggi vecchie risposte su Stack Overflow che chiamano .decode("utf-8") sul token codificato, quel codice è dell'era PyJWT 1.x e genererà un AttributeError nella versione 2.x. Il token è già una stringa — usalo direttamente.

Ciclo Completo con Scadenza

Python 3.10+ — codifica poi decodifica con exp
import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "webhook-processor-secret"

# Crea un token che scade in 1 ora
payload = {
    "sub": "svc_payment_processor",
    "iss": "auth.interno.esempio.com",
    "aud": "https://api.esempio.com",
    "exp": datetime.now(timezone.utc) + timedelta(hours=1),
    "permissions": ["orders:read", "refunds:create"],
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")

# In seguito, quando il token arriva nell'header della richiesta:
try:
    decoded = jwt.decode(
        token,
        SECRET_KEY,
        algorithms=["HS256"],
        audience="https://api.esempio.com",
        issuer="auth.interno.esempio.com",
    )
    print(f"Servizio: {decoded['sub']}")
    print(f"Permessi: {decoded['permissions']}")
except jwt.ExpiredSignatureError:
    print("Token scaduto — richiedi una nuova autenticazione")
except jwt.InvalidAudienceError:
    print("Token non destinato a questa API")
except jwt.InvalidIssuerError:
    print("Token emesso da un'autorità sconosciuta")
Nota:PyJWT converte gli oggetti datetime in timestamp Unix automaticamente durante la codifica. Durante la decodifica, i claim exp, iat e nbf vengono restituiti come interi, non come oggetti datetime. Devi convertirli tu stesso con datetime.fromtimestamp(payload["exp"], tz=timezone.utc).

Decodifica JWT Senza Verifica della Firma

A volte è necessario leggere i claim prima di poter verificare il token. Uno scenario comune: l'header del token contiene un kid (key ID) e devi recuperare la chiave pubblica corrispondente da un endpoint JWKS prima di poter verificare. PyJWT supporta questo con l'opzione verify_signature: False. Passi comunque l'elenco algorithms, ma l'argomento key viene ignorato.

Python 3.10+ — decodifica non verificata
import jwt

token = (
    "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNpZy0xNzI2In0"
    ".eyJzdWIiOiJ1c3JfM2M3ZiIsInNjb3BlIjoicmVhZDpvcmRlcnMiLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0"
    ".signature_placeholder"
)

# Passo 1: leggi i claim senza verifica per ottenere info di routing
unverified = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["RS256"]
)
print(unverified)
# {'sub': 'usr_3c7f', 'scope': 'read:orders', 'iss': 'auth.example.com'}

# Passo 2: leggi l'header per sapere quale chiave usare
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'RS256', 'typ': 'JWT', 'kid': 'sig-1726'}
# Ora usa header['kid'] per recuperare la chiave pubblica corretta dal tuo endpoint JWKS
Avviso:I token non verificati non sono affidabili. Usa questo pattern solo per decisioni di routing (quale chiave recuperare, quale tenant cercare). Non prendere mai decisioni di autorizzazione basate su claim non verificati. Un attaccante può inserire qualsiasi cosa nel payload.

C'è una distinzione sottile. jwt.get_unverified_header() legge solo l'header — il primo segmento. La chiamata jwt.decode() con verify_signature: False legge il payload (secondo segmento). Tra i due puoi estrarre tutto da un token senza una chiave. PyJWT valida comunque che il token abbia la struttura corretta (tre segmenti separati da punti, base64 valido, JSON valido) anche quando la verifica della firma è disattivata. Se il token è strutturalmente malformato, solleva DecodeError indipendentemente dalle opzioni che passi.

Riferimento Parametri jwt.decode()

La firma completa è jwt.decode(jwt, key, algorithms, options, audience, issuer, leeway, require). Tutti i parametri dopo algorithms sono solo keyword.

Parametro
Tipo
Default
Descrizione
jwt
str | bytes
(obbligatorio)
La stringa JWT codificata da decodificare
key
str | bytes | dict
(obbligatorio)
Chiave segreta (HMAC) o chiave pubblica (RSA/EC) per la verifica
algorithms
list[str]
(obbligatorio)
Algoritmi consentiti — es. ["HS256"], ["RS256"]. Non omettere mai questo parametro.
options
dict
{}
Sovrascrive i flag di verifica: verify_signature, verify_exp, verify_aud, ecc.
audience
str | list[str]
None
Claim aud atteso — solleva InvalidAudienceError in caso di mancata corrispondenza
issuer
str
None
Claim iss atteso — solleva InvalidIssuerError in caso di mancata corrispondenza
leeway
timedelta | int
0
Secondi di tolleranza per desfasamento orario nei controlli exp e nbf
require
list[str]
[]
Claim che devono essere presenti — solleva MissingRequiredClaimError in caso contrario

Il dict options offre un controllo granulare su quali validazioni esegue PyJWT. Le chiavi corrispondono a controlli individuali: verify_signature, verify_exp, verify_nbf, verify_iss, verify_aud, e verify_iat. Tutti hanno default True a meno che non vengano impostati esplicitamente a False. In produzione, lascia tutti questi valori ai loro default. Disabilito singoli controlli solo durante lo sviluppo quando lavoro con token di test obsoleti e ho bisogno di bypassare temporaneamente la scadenza.

Python 3.10+ — uso di options e require
import jwt

# Richiede che siano presenti claim specifici — solleva MissingRequiredClaimError se assenti
payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"require": ["exp", "iss", "sub"]},
    issuer="auth.interno.esempio.com",
)

# Solo in sviluppo: salta la scadenza per testare con token vecchi
dev_payload = jwt.decode(
    token,
    SECRET_KEY,
    algorithms=["HS256"],
    options={"verify_exp": False},  # NON usare in produzione
)

Decodifica JWT Manuale con base64 e json

Puoi decodificare un payload JWT usando solo la libreria standard Python — senza pip install necessario. Questo è genuinamente utile in diverse situazioni: script di debug dove aggiungere una dipendenza è eccessivo, ambienti CI con restrizioni dove è disponibile solo la libreria standard, funzioni AWS Lambda dove vuoi minimizzare il tempo di avvio a freddo, o semplicemente per capire cosa sia effettivamente un JWT. Il processo è semplice: dividi sui punti, prendi il segmento che vuoi, aggiungi il padding base64, decodifica e analizza il JSON.

I moduli base64 e json fanno entrambi parte della libreria standard Python, quindi questo approccio funziona su qualsiasi installazione Python dalla 3.6 in poi. Le funzioni seguenti gestiscono separatamente l'header (primo segmento) e il payload (secondo segmento):

Python 3.10+ — decodifica JWT manuale senza librerie
import base64
import json

def decode_jwt_payload(token: str) -> dict:
    """Decodifica il payload JWT senza verifica della firma.
    Funziona con qualsiasi JWT — HS256, RS256, ES256, ecc.
    """
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError(f"Expected 3 JWT segments, got {len(parts)}")

    payload_b64 = parts[1]
    # base64url usa - e _ invece di + e /
    # urlsafe_b64decode di Python gestisce questo, ma richiede il padding
    payload_b64 += "=" * (-len(payload_b64) % 4)
    payload_bytes = base64.urlsafe_b64decode(payload_b64)
    return json.loads(payload_bytes)


def decode_jwt_header(token: str) -> dict:
    """Decodifica l'header JWT (algoritmo, key ID, tipo)."""
    header_b64 = token.split(".")[0]
    header_b64 += "=" * (-len(header_b64) % 4)
    return json.loads(base64.urlsafe_b64decode(header_b64))


# Esempio d'uso
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTgxNTYwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

header = decode_jwt_header(token)
print(f"Algorithm: {header['alg']}")
# Algorithm: HS256

claims = decode_jwt_payload(token)
print(f"Subject: {claims['sub']}")
print(f"Role: {claims['role']}")
# Subject: usr_8f2a
# Role: admin

Il trucco del padding (+= "=" * (-len(s) % 4)) è la parte che tutti dimenticano. Il base64url di JWT omette i caratteri = finali, ma urlsafe_b64decode di Python li richiede. Senza la correzione del padding, ottieni un binascii.Error: Incorrect padding.

Nota:La decodifica manuale non verifica la firma. Chiunque può modificare il payload di un JWT e ricodificarlo. Usa questo approccio solo per ispezione e debug, mai per la logica di autorizzazione. Per qualsiasi cosa importante, usa jwt.decode() con una chiave reale.

Decodifica JWT da Risposte API e File Token

I due scenari reali più comuni: estrarre un JWT da una risposta HTTP (un endpoint token OAuth, un'API di login), e leggere token da file (credenziali di account di servizio, segreti montati Kubernetes, token in cache su disco). Entrambi richiedono una gestione adeguata degli errori. Le richieste di rete falliscono. I file scompaiono. I token scadono tra il momento in cui vengono messi in cache e il momento in cui vengono letti.

Gli esempi seguenti usano httpx per le chiamate HTTP (sostituisci con requests se preferisci, il pattern è identico) e pathlib.Path per le operazioni sui file. Ogni esempio intercetta eccezioni PyJWT specifiche anziché un generico except Exception, così puoi rispondere appropriatamente a ogni modalità di errore: ri-autenticazione alla scadenza, allerta per errori di firma, retry in caso di timeout di rete.

Decodifica JWT da una Risposta API

Python 3.10+ — decodifica JWT da endpoint token OAuth
import jwt
import httpx  # or requests

TOKEN_ENDPOINT = "https://auth.esempio.com/oauth/token"
SECRET_KEY = "shared-webhook-signing-key"

def get_and_decode_token() -> dict:
    """Recupera un access token dall'auth server e lo decodifica."""
    try:
        response = httpx.post(
            TOKEN_ENDPOINT,
            data={
                "grant_type": "client_credentials",
                "client_id": "svc_order_processor",
                "client_secret": "cs_9f3a7b2e",
            },
            timeout=10.0,
        )
        response.raise_for_status()
    except httpx.HTTPError as exc:
        raise RuntimeError(f"Token request failed: {exc}") from exc

    token_data = response.json()
    access_token = token_data["access_token"]

    try:
        payload = jwt.decode(
            access_token,
            SECRET_KEY,
            algorithms=["HS256"],
            audience="https://api.esempio.com",
        )
        return payload
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Invalid token from auth server: {exc}") from exc


claims = get_and_decode_token()
print(f"Service: {claims['sub']}, Scopes: {claims.get('scope', 'none')}")

Decodifica JWT da un File

Python 3.10+ — leggi e decodifica un file token in cache
import jwt
from pathlib import Path
from datetime import datetime, timezone

TOKEN_PATH = Path("/var/run/secrets/service-account-token")
PUBLIC_KEY_PATH = Path("/etc/ssl/auth/public_key.pem")

def decode_token_from_file() -> dict:
    """Legge un JWT da un file e lo verifica con una chiave pubblica PEM."""
    try:
        token = TOKEN_PATH.read_text().strip()
        public_key = PUBLIC_KEY_PATH.read_text()
    except FileNotFoundError as exc:
        raise RuntimeError(f"Missing file: {exc.filename}") from exc

    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience="https://internal-api.esempio.com",
        )
    except jwt.ExpiredSignatureError:
        exp_time = jwt.decode(
            token,
            options={"verify_signature": False},
            algorithms=["RS256"],
        ).get("exp", 0)
        expired_at = datetime.fromtimestamp(exp_time, tz=timezone.utc)
        raise RuntimeError(f"Token expired at {expired_at.isoformat()}")
    except jwt.InvalidTokenError as exc:
        raise RuntimeError(f"Token verification failed: {exc}") from exc

    return payload


claims = decode_token_from_file()
print(f"Subject: {claims['sub']}, Issuer: {claims['iss']}")

Decodifica JWT da Riga di Comando

A volte hai solo bisogno di ispezionare un token dal terminale senza scrivere uno script. Magari stai facendo debug di un flusso OAuth e vuoi vedere cosa c'è nell'header Authorization, oppure hai recuperato un token dai DevTools del browser e vuoi controllarne la scadenza. Il flag -c di Python rende tutto questo un one-liner. Pipe il token e ottieni i claim come JSON formattato. Nessun file di script, nessun ambiente virtuale.

Bash
# Decodifica payload JWT dagli appunti o da una variabile (senza verifica)
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
"
# {
#   "sub": "usr_8f2a",
#   "role": "admin"
# }
Bash
# Decodifica l'header JWT per verificare algoritmo e key ID
echo "eyJhbGciOiJSUzI1NiIsImtpZCI6InNpZy0xNzI2In0.payload.sig" \
  | python3 -c "
import sys, base64, json
token = sys.stdin.read().strip()
header = token.split('.')[0]
header += '=' * (-len(header) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(header)), indent=2))
"
# {
#   "alg": "RS256",
#   "kid": "sig-1726"
# }
Bash
# Se PyJWT è installato, verifica e decodifica in un solo passaggio
python3 -c "
import jwt, sys, json
token = sys.argv[1]
payload = jwt.decode(token, options={'verify_signature': False}, algorithms=['HS256'])
print(json.dumps(payload, indent=2))
" "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.sig"

Per un'alternativa visiva senza nessuna configurazione del terminale, incolla il token nel JWT Decoder di ToolDeck e vedi istantaneamente header, payload e stato di verifica della firma.

python-jose e Altre Alternative

python-jose è una libreria JWT alternativa che supporta nativamente JWS, JWE (token cifrati) e JWK. Se la tua applicazione deve gestire JWT cifrati (JWE) — dove il payload stesso è cifrato, non solo firmato — python-jose è la scelta giusta perché PyJWT non supporta JWE affatto. La libreria ha anche la gestione integrata di set di chiavi JWKS, che semplifica l'integrazione con provider di identità come Auth0, Okta o Keycloak che espongono set di chiavi rotanti. L'interfaccia di decodifica è quasi identica a PyJWT, quindi passare da una all'altra richiede modifiche minime al codice:

Python 3.10+ — python-jose
# pip install python-jose[cryptography]
from jose import jwt as jose_jwt

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.signature"

# Decodifica verificata — stesso pattern di PyJWT
payload = jose_jwt.decode(
    token,
    "signing-secret-key",
    algorithms=["HS256"],
    audience="https://api.esempio.com",
)
print(payload)
# {'sub': 'usr_8f2a', 'scope': 'orders:read'}

# Decodifica non verificata
claims = jose_jwt.get_unverified_claims(token)
header = jose_jwt.get_unverified_header(token)
print(f"Algorithm: {header['alg']}, Subject: {claims['sub']}")

La mia raccomandazione: inizia con PyJWT. Copre il 95% dei casi d'uso JWT, ha la comunità più ampia e l'API è pulita. Passa a python-jose se hai bisogno del supporto JWE o preferisci la sua gestione JWKS. Una terza opzione degna di nota è Authlib, che include la gestione JWT all'interno di un framework OAuth/OIDC molto più ampio. Se stai già usando Authlib per i flussi client OAuth, il suo modulo authlib.jose.jwt ti evita di aggiungere una seconda dipendenza JWT. Altrimenti, è una dipendenza pesante solo per la decodifica dei token.

Output nel Terminale con Syntax Highlighting

Leggere i claim JWT grezzi in un terminale va bene per controlli rapidi, ma quando fai debug dei payload dei token regolarmente (io lo facevo quotidianamente mentre sviluppavo un gateway di autenticazione interno a Milano), l'output colorato fa davvero la differenza. Valori stringa, numeri, booleani e null vengono visualizzati in colori distinti, il che significa che puoi individuare un permesso mancante o un timestamp di scadenza errato a colpo d'occhio senza leggere ogni singolo carattere.

La libreria rich (pip install rich) ha una funzione print_json che accetta una stringa JSON o un dict Python e lo stampa con syntax highlighting completo nel terminale. Combinala con PyJWT per un flusso di ispezione JWT in due righe:

Python 3.10+ — output rich nel terminale
# pip install rich PyJWT
import jwt
from rich import print_json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiIsInBlcm1pc3Npb25zIjpbIm9yZGVyczpyZWFkIiwicmVmdW5kczpjcmVhdGUiXSwiZXhwIjoxNzExODE1NjAwfQ.sig"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)

# Output JSON colorato e indentato nel terminale
print_json(data=payload)
# {
#   "sub": "usr_8f2a",           ← stringhe in verde
#   "role": "admin",
#   "permissions": [
#     "orders:read",
#     "refunds:create"
#   ],
#   "exp": 1711815600            ← numeri in ciano
# }
Nota:L'output di rich contiene codici di escape ANSI. Non scriverlo su file o restituirlo da endpoint API — è solo per la visualizzazione nel terminale. Usa json.dumps() quando hai bisogno di output in testo semplice.

Elaborazione di Grandi Batch di Token

I token JWT sono piccoli (tipicamente sotto i 2 KB ciascuno), ma esistono scenari in cui li elabori in blocco. Analisi dei log di audit dopo un incidente di sicurezza. Script di migrazione delle sessioni quando si cambia provider di autenticazione. Validazione batch per conformità normativa dove devi dimostrare che ogni token emesso negli ultimi 90 giorni è stato firmato con la chiave corretta. Se hai decine di migliaia di token in un file di log NDJSON, elaborarli riga per riga evita di caricare l'intero file in memoria e ti permette di riportare i risultati in modo incrementale.

Validazione Batch di Token da un Log di Audit

Python 3.10+ — validazione token in streaming
import jwt
import json
from pathlib import Path

SECRET_KEY = "audit-log-signing-key"

def validate_token_log(log_path: str) -> dict:
    """Elabora un file NDJSON dove ogni riga ha un campo 'token'.
    Restituisce i conteggi di token validi, scaduti e non validi.
    """
    stats = {"valid": 0, "expired": 0, "invalid": 0}

    with open(log_path) as fh:
        for line_num, line in enumerate(fh, 1):
            line = line.strip()
            if not line:
                continue

            try:
                record = json.loads(line)
                token = record["token"]
            except (json.JSONDecodeError, KeyError):
                stats["invalid"] += 1
                continue

            try:
                jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
                stats["valid"] += 1
            except jwt.ExpiredSignatureError:
                stats["expired"] += 1
            except jwt.InvalidTokenError:
                stats["invalid"] += 1

    return stats


result = validate_token_log("auth-events-2026-03.ndjson")
print(f"Valid: {result['valid']}, Expired: {result['expired']}, Invalid: {result['invalid']}")
# Valid: 14832, Expired: 291, Invalid: 17

Estrazione dei Claim da un Export NDJSON di Token

Python 3.10+ — estrai e trasforma i claim da un log di token
import base64
import json
from datetime import datetime, timezone

def extract_claims_stream(input_path: str, output_path: str):
    """Legge i token riga per riga, decodifica i payload, scrive i claim appiattiti."""
    with open(input_path) as infile, open(output_path, "w") as outfile:
        for line in infile:
            line = line.strip()
            if not line:
                continue

            record = json.loads(line)
            token = record.get("access_token", "")
            parts = token.split(".")
            if len(parts) != 3:
                continue

            payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
            claims = json.loads(base64.urlsafe_b64decode(payload_b64))

            # Appiattisci in un record adatto all'audit
            flat = {
                "timestamp": record.get("timestamp"),
                "subject": claims.get("sub"),
                "issuer": claims.get("iss"),
                "expired_at": datetime.fromtimestamp(
                    claims.get("exp", 0), tz=timezone.utc
                ).isoformat(),
            }
            outfile.write(json.dumps(flat) + "\n")

extract_claims_stream("token-audit.ndjson", "claims-extract.ndjson")
Nota:Per file sotto qualche centinaio di MB, la lettura riga per riga è sufficientemente efficiente. Se raggiungi i limiti di prestazioni su dump di token molto grandi, considera l'uso di multiprocessing.Pool per distribuire la validazione su più core, poiché ogni token è indipendente.

Errori Comuni

Questi quattro errori emergono ripetutamente nelle revisioni del codice e nelle domande su Stack Overflow. Ognuno è facile da commettere e il messaggio di errore che PyJWT fornisce non punta sempre direttamente alla causa. Ho visto solo il problema di naming dei pacchetti sprecare ore di debug quando qualcuno installa la libreria sbagliata e ottiene un'API completamente inaspettata.

Confusione tra i nomi dei pacchetti PyJWT e python-jwt

Problema: Eseguire pip install jwt o pip install python-jwt installa un pacchetto completamente diverso. import jwt poi fallisce o ti dà un'API che non riconosci.

Soluzione: Installa sempre con pip install PyJWT. L'import è import jwt. Verifica con pip show PyJWT che sia installato il pacchetto corretto.

Before · Python
After · Python
pip install jwt
# or
pip install python-jwt

import jwt  # pacchetto sbagliato — API completamente diversa
pip install PyJWT

import jwt  # corretto — questo è PyJWT
print(jwt.__version__)  # 2.x
Omissione del parametro algorithms

Problema: In PyJWT 1.x, algorithms era opzionale e consentiva qualsiasi algoritmo per impostazione predefinita. Questo creava una vulnerabilità di sicurezza in cui un attaccante poteva impostare alg: none. PyJWT 2.x ora solleva DecodeError se algorithms è assente.

Soluzione: Passa sempre algorithms come elenco esplicito. Usa solo gli algoritmi con cui la tua applicazione emette effettivamente i token.

Before · Python
After · Python
# PyJWT 2.x — questo solleva DecodeError
payload = jwt.decode(token, SECRET_KEY)
# DecodeError: algorithms must be specified
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
Uso di jwt.decode() con il tipo di chiave errato per RS256

Problema: Passare un segreto stringa a jwt.decode() con algorithms=["RS256"] solleva InvalidSignatureError. RS256 richiede una chiave pubblica in formato PEM, non una stringa segreta condivisa.

Soluzione: Carica la chiave pubblica PEM da un file o una variabile d'ambiente. Installa il pacchetto cryptography: pip install PyJWT cryptography.

Before · Python
After · Python
# Questo fallisce — RS256 richiede una chiave pubblica, non un segreto stringa
payload = jwt.decode(token, "my-secret", algorithms=["RS256"])
# InvalidSignatureError
public_key = open("public_key.pem").read()
payload = jwt.decode(token, public_key, algorithms=["RS256"])
Dimenticare il padding base64 nella decodifica manuale

Problema: La codifica base64url di JWT elimina i caratteri = finali. base64.urlsafe_b64decode di Python solleva binascii.Error: Incorrect padding se passi il segmento grezzo senza correggere il padding.

Soluzione: Aggiungi il padding prima di decodificare: segment += '=' * (-len(segment) % 4). Questa formula produce sempre il numero corretto di caratteri di padding (0, 1, 2 o 3).

Before · Python
After · Python
import base64
payload_b64 = token.split(".")[1]
data = base64.urlsafe_b64decode(payload_b64)
# binascii.Error: Incorrect padding
import base64
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)
data = base64.urlsafe_b64decode(payload_b64)  # funziona

PyJWT vs Alternative — Confronto Rapido

Metodo
Verifica Firma
Valida Claim
Tipi Custom
Richiede Installazione
PyJWT jwt.decode()
✓ (exp, aud, iss, nbf)
N/D (restituisce dict)
pip install PyJWT
PyJWT decode non verificato
N/D
pip install PyJWT
Decode manuale base64
N/D
No (stdlib)
python-jose jwt.decode()
N/D
pip install python-jose
Authlib jwt.decode()
N/D
pip install Authlib
PyJWT + cryptography
✓ (RSA/EC)
N/D
pip install PyJWT cryptography

PyJWT è il punto di partenza giusto per la maggior parte delle applicazioni Python. Copre HMAC e (con il backend cryptography) la verifica delle firme RSA ed EC. Se hai bisogno di JWE (token cifrati), passa a python-jose o Authlib. La decodifica manuale base64 funziona per il debug ma non offre alcuna garanzia di sicurezza.

Ecco quando scelgo ciascuna opzione: PyJWT per qualsiasi servizio web standard che fa verifica HS256 o RS256. python-jose quando l'architettura include token cifrati o rotazione JWKS. Base64 manuale per ispezioni rapide in ambienti dove pip non è disponibile (container CI, host di produzione con restrizioni, avvii a freddo AWS Lambda dove si vuole minimizzare le dipendenze). Authlib quando il progetto lo usa già per i flussi client OAuth e aggiungere un'altra libreria JWT sarebbe ridondante.

Per un'alternativa senza codice, incolla qualsiasi token nel JWT Decoder per vedere l'header e il payload decodificati con feedback di validazione dei claim.

Domande Frequenti

Come decodifico un JWT in Python senza verificare la firma?

Passa options={"verify_signature": False} e algorithms=["HS256"] a jwt.decode(). Questo restituisce il dict del payload senza controllare la firma. Usalo solo per ispezione — leggere i claim prima di recuperare la chiave pubblica corretta, o per debug in sviluppo. Non saltare mai la verifica su token che proteggono l'accesso a risorse reali.

Python
import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOGYyYSIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

payload = jwt.decode(
    token,
    options={"verify_signature": False},
    algorithms=["HS256"]
)
print(payload)
# {'sub': 'usr_8f2a', 'role': 'admin'}

Qual è la differenza tra PyJWT e python-jwt?

PyJWT (pip install PyJWT, import jwt) è la libreria JWT più diffusa per Python, con oltre 80 milioni di download mensili. python-jwt (pip install python_jwt) è una libreria separata, molto meno usata, con una superficie API diversa. Se vedi import jwt nel codice di qualcuno, sta usando PyJWT. La confusione nasce dal nome del pacchetto PyPI (PyJWT) che differisce dal nome di import (jwt). Usa PyJWT a meno che tu non abbia un motivo specifico per fare altrimenti.

Come decodifico un JWT con RS256 in Python?

Installa sia PyJWT che il backend cryptography: pip install PyJWT cryptography. Poi passa la chiave pubblica in formato PEM come argomento key e algorithms=["RS256"]. PyJWT delega la verifica della firma RSA alla libreria cryptography. Senza il pacchetto cryptography installato, PyJWT solleva un errore quando si tenta di usare algoritmi RSA o EC.

Python
import jwt

public_key = open("public_key.pem").read()

payload = jwt.decode(
    token,
    public_key,
    algorithms=["RS256"],
    audience="https://api.example.com"
)

Perché PyJWT solleva ExpiredSignatureError?

PyJWT controlla il claim exp (scadenza) per impostazione predefinita. Se l'ora UTC corrente è successiva al timestamp exp, solleva jwt.ExpiredSignatureError. Puoi aggiungere tolleranza per desfasamento orario con il parametro leeway: jwt.decode(token, key, algorithms=["HS256"], leeway=timedelta(seconds=30)). Questo concede un margine di 30 secondi. Per disabilitare completamente il controllo di scadenza (sconsigliato in produzione), passa options={"verify_exp": False}.

Python
import jwt
from datetime import timedelta

try:
    payload = jwt.decode(token, secret, algorithms=["HS256"], leeway=timedelta(seconds=30))
except jwt.ExpiredSignatureError:
    print("Token scaduto — richiedi una nuova autenticazione")

Posso leggere i claim JWT senza alcuna libreria in Python?

Sì. Dividi il token sui punti, prendi il secondo segmento (il payload), aggiungici caratteri = per rendere la lunghezza un multiplo di 4, poi decodificalo con base64url e analizza il JSON risultante. Questo ti restituisce il dict dei claim ma non verifica la firma. È utile in ambienti con restrizioni dove non puoi installare PyJWT, o per script di debug rapidi.

Python
import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOGYyYSJ9.signature"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4)  # fix padding
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
print(claims)  # {'sub': 'usr_8f2a'}

Come valido il claim audience con PyJWT?

Passa il parametro audience a jwt.decode(): jwt.decode(token, key, algorithms=["HS256"], audience="https://api.example.com"). PyJWT confronta il claim aud nel token con il valore che fornisci. Se il token non ha un claim aud, o il valore non corrisponde, solleva jwt.InvalidAudienceError. Puoi anche passare un elenco di audience accettabili se il tuo servizio accetta token destinati a più API.

Python
import jwt

payload = jwt.decode(
    token,
    secret,
    algorithms=["HS256"],
    audience=["https://api.example.com", "https://admin.example.com"]
)

Strumenti Correlati

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria SantosRevisore tecnico

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.