Skip to content
Torna al blog
Sicurezza

Come decodificare un token JWT: guida completa per sviluppatori

Decodifica JWT in sicurezza: anatomia, base64url e codice per Node.js, Python e Go. Ispeziona header, payload e claim con decodificatore JWT online e privato.

12 min di lettura

Come decodificare un token JWT: guida completa per sviluppatori

La tua API ha appena restituito 401 Unauthorized. L’header Authorization: Bearer eyJhbGciOi... sembra corretto. Il token era scaduto, l’audience era sbagliata o l’issuer ha ruotato una chiave? Non puoi rispondere a questa domanda senza leggere cosa c’è davvero dentro il token, e per decodificare un token JWT non ti servono né un secret, né una libreria, né tantomeno una connessione di rete. Un JWT è composto da tre blocchi codificati in base64url e uniti da punti. Decodificarlo è un’operazione meccanica: split, base64url, JSON.parse. Nessuna magia, nessuna crittografia.

Questa guida ripercorre l’anatomia del JWT, mostra come decodificarlo in Node.js, Python, Go e nel browser, chiarisce la distinzione tra decode e verify che manda in confusione la maggior parte dei team, ed elenca le vere modalità di errore che ti faranno tribolare. Se ti serve solo ispezionare un token subito, vai direttamente al nostro decodificatore JWT gratuito. Gira interamente nel tuo browser, così i token di produzione non lasciano mai il tuo dispositivo.

Cos’è un JWT? (anatomia rapida)

Un JSON Web Token (JWT) è una credenziale compatta e URL-safe definita nell’RFC 7519. Trasporta claim (informazioni sull’utente e sul token stesso) tra due parti. Un JWT è costituito da tre blocchi codificati in base64url e uniti da punti: un header, un payload e una signature.

Ecco un token reale smontato pezzo per pezzo per farti vedere la struttura:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9       ← header
.
eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0   ← payload
.
4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0       ← signature

L’header descrive come il token è firmato, di solito { "alg": "HS256", "typ": "JWT" }. Il payload trasporta i claim: quelli registrati come sub, exp, iat più quelli personalizzati come role o tenant. La signature è una prova crittografica calcolata su header e payload che permette al destinatario di individuare manomissioni. Base64url è una variante URL-safe di base64; per un’introduzione di dieci minuti leggi la nostra guida Base64 per principianti.

Incontrerai JWT ovunque viva l’autenticazione moderna: access token OAuth 2.0, ID token OpenID Connect, credenziali API emesse da Auth0, Okta, Clerk, Supabase, Firebase, e token scambiati tra microservizi all’interno di una mesh. Sono il formato di credenziali predefinito dell’ultimo decennio.

Ricorda: i JWT sono codificati, non cifrati. Chiunque possieda il token può leggere ogni claim. La firma dimostra l’origine; non nasconde i contenuti. Da qui discende il resto: cosa è sicuro inserire nel payload, perché la decodifica non ha bisogno di un secret, perché la verifica della firma è irrinunciabile lato server.

Come funziona la decodifica JWT (base64url, non decifratura)

Decodificare un JWT non è un’operazione crittografica. Sono quattro passaggi meccanici:

  1. Spezza il token su . in esattamente tre segmenti.
  2. Decodifica in base64url il primo segmento e fai il parsing JSON: ecco l’header.
  3. Decodifica in base64url il secondo segmento e fai il parsing JSON: ecco il payload.
  4. Lascia il terzo segmento (la signature) come byte grezzi; per verificarlo serve la chiave.

Questo è tutto l’algoritmo. Nessuna libreria è obbligatoria. Qualsiasi linguaggio con base64 e un parser JSON può decodificare un JWT in cinque righe. Il nostro codificatore/decodificatore Base64 ti fa eseguire a mano i passaggi 2 e 3 se vuoi toccare con mano la meccanica.

Cos’è base64url?

Base64url è il base64 classico con tre ritocchi che rendono l’output sicuro in URL e header HTTP: - sostituisce +, _ sostituisce /, e il padding finale = viene eliminato. Se dai in pasto a un decoder base64 standard del base64url grezzo senza invertire quelle sostituzioni, ottieni o spazzatura o un errore. La guida avanzata a Base64 tratta in profondità i casi limite del padding.

Base64 standardbase64url
AlfabetoA-Z a-z 0-9 + /A-Z a-z 0-9 - _
Padding= obbligatorio in codaRimosso
URL safe?No
EsempioPDw/Pz8+PDw_Pz8-

Altro punto da fissare: non puoi decifrare la signature lato client. La decodifica è a senso unico, da byte codificati a JSON. Verificare la signature è un’operazione separata che richiede o il secret HMAC (per gli algoritmi della famiglia HS) o la chiave pubblica dell’issuer (per RS, PS, ES, EdDSA).

Perché non ti serve il secret per decodificare

Perché il payload è base64url più JSON, non testo cifrato. Un secret entra in gioco solo quando vuoi dimostrare che il token non è stato manomesso, ovvero il controllo della signature. Chiunque si trovi sul percorso di rete, chiunque abbia il token in una riga di log, chiunque con un browser può leggere ogni claim che ci metti dentro. Ecco perché non devi mai inserire password, API key o PII oltre a ciò che il destinatario già conosce nel payload di un JWT. Per il threat model completo, leggi la nostra guida alle best practice di sicurezza.

Decodifica JWT online in 3 clic — decodificatore JWT gratuito

A volte ti serve una risposta subito: il token è scaduto, il claim aud è quello che pensi, l’header dice alg:none? La strada più rapida è il nostro decodificatore JWT online. È costruito per il turno di reperibilità delle 2 di notte.

  1. Incolla il token completo nell’area di input. Includi tutti e tre i segmenti separati da punti.
  2. Leggi l’header decodificato, il payload e i chip di stato in alto: algoritmo, issued-at, scadenza, e un badge rosso Expired se exp è già nel passato.
  3. Copia il pannello che ti serve nel tuo bug report, nel thread Slack o nella tua fixture di test.

Perché è sicuro anche per token di produzione reali:

  • 100% browser-based. La decodifica avviene tramite atob nativo e JSON.parse. Nessuna richiesta di rete, mai.
  • Nessun logging, nessun tracking, nessun cookie, nessuna registrazione.
  • Funziona offline una volta caricata la pagina.

Il decodificatore JWT è agnostico rispetto all’algoritmo. Siccome la decodifica ha bisogno solo di base64url e JSON, legge ogni variante JWS: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA e alg:none. Solo la verifica della signature dipende dall’algoritmo, e la verifica non è qualcosa che vuoi far fare a uno strumento web pubblico; ci torneremo sotto.

Ti serve decodificare manualmente in base64 un segmento per fare un controllo incrociato con lo strumento? Usa il nostro codificatore/decodificatore Base64 e passagli ogni segmento come base64url.

Come decodificare JWT nel codice (Node.js, Python, Go, browser)

Per tutto ciò che non è debug interattivo (middleware, test, script di migrazione, strumenti CLI) ti affiderai a una libreria. Ecco il codice minimo per decodificare un JWT nei quattro ambienti in cui più probabilmente ti imbatterai, con il percorso read-only e quello di verifica affiancati. Ogni snippet è copia-incollabile e produce gli output mostrati nei commenti.

Decodificare JWT in Node.js (jsonwebtoken)

// npm install jsonwebtoken
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' +
              '.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0' +
              '.4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0';

// Solo decodifica — NON verifica la signature
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header);   // { alg: 'HS256', typ: 'JWT' }
console.log(decoded.payload);  // { sub: 'user_123', exp: 1999999999 }

// Verifica — il percorso di produzione
const secret = process.env.JWT_SECRET;
const verified = jwt.verify(token, secret, { algorithms: ['HS256'] });

Passa sempre una allowlist algorithms esplicita a verify. Ometterla ha storicamente permesso agli attaccanti di degradare un token RS256 a HS256 firmando con la chiave pubblica come secret HMAC, il classico attacco di algorithm-confusion. L’allowlist è la tua difesa.

Decodificare JWT in Python (PyJWT)

# pip install PyJWT
import jwt

token = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
    ".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0"
    ".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"
)

# Solo decodifica — non sicuro per l'auth, va bene per l'ispezione
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded)  # {'sub': 'user_123', 'exp': 1999999999}

# Header senza toccare il payload
header = jwt.get_unverified_header(token)
print(header)   # {'alg': 'HS256', 'typ': 'JWT'}

# Verifica — percorso di produzione
payload = jwt.decode(
    token,
    key="your-hs256-secret",
    algorithms=["HS256"],
    audience="api.example.com",
)

PyJWT rifiuta di verificare senza una lista algorithms, un default ragionevole che previene lo stesso attacco di confusione di cui avverte l’esempio Node.

Decodificare JWT in Go (golang-jwt/jwt/v5)

// go get github.com/golang-jwt/jwt/v5
package main

import (
    "fmt"
    "github.com/golang-jwt/jwt/v5"
)

func main() {
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
        ".eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0" +
        ".4NhxPjwoZxPNuxG-2C5ugGxaUsUJ0QyskAz7Ymz5Sg0"

    // Solo decodifica
    parser := jwt.NewParser()
    claims := jwt.MapClaims{}
    _, _, err := parser.ParseUnverified(tokenString, claims)
    if err != nil {
        panic(err)
    }
    fmt.Println(claims["sub"], claims["exp"]) // user_123 1.999999999e+09

    // Verifica
    secret := []byte("your-hs256-secret")
    token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
        if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
        }
        return secret, nil
    })
    fmt.Println(token.Valid, err)
}

La closure keyFunc è il punto in cui imponi la famiglia di algoritmi: rifiuta qualsiasi cosa non sia il metodo che ti aspetti prima di restituire la chiave.

Decodificare JWT nel browser (zero dipendenze)

A volte non vuoi una dipendenza: un pannello di debug veloce, un’estensione del browser, un piccolo badge UI che mostra il ruolo dell’utente corrente. Le API native del browser bastano:

function decodeJwt(token) {
  const [h, p] = token.split('.');
  const pad = (s) => s + '==='.slice((s.length + 3) % 4);
  const decodeSegment = (s) => {
    const b64 = pad(s).replace(/-/g, '+').replace(/_/g, '/');
    const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
    return JSON.parse(new TextDecoder().decode(bytes));
  };
  return { header: decodeSegment(h), payload: decodeSegment(p) };
}

const { header, payload } = decodeJwt(token);
console.log(header);   // { alg: 'HS256', typ: 'JWT' }
console.log(payload);  // { sub: 'user_123', exp: 1999999999 }

Il passaggio con TextDecoder è cruciale per qualsiasi token con payload non-ASCII (emoji in un display name, cirillico in un preferred_username): atob da solo restituisce una stringa binaria, che rompe JSON.parse su UTF-8 multi-byte. È esattamente quello che il nostro decodificatore JWT online esegue localmente nel tuo browser, senza la UI.

Tabella comparativa

LinguaggioSolo decodificaVerificaLibreria
Node.jsjwt.decode(token)jwt.verify(token, key, { algorithms: [...] })jsonwebtoken
Pythonjwt.decode(token, options={"verify_signature": False})jwt.decode(token, key, algorithms=[...])PyJWT
Goparser.ParseUnverified(token, claims)jwt.Parse(token, keyFunc)golang-jwt/jwt/v5
Browseratob + TextDecoder + JSON.parseChiedi al tuo backend

Decode vs Verify — la differenza cruciale

Decodificare un JWT legge i suoi claim; verificare un JWT dimostra che quei claim non sono stati manomessi. La decodifica non si fida mai; la verifica è la fiducia. Questa è l’unica distinzione che separa un’implementazione di autenticazione funzionante da una CVE.

DecodeVerify
Serve secret/chiave?No
Va eseguita lato client?Sì, in sicurezzaMai
Dimostra l’autenticità?No
Controlla la scadenza?Opzionale
Caso d’usoDebug, ispezioneAutenticazione, autorizzazione

Non prendere mai una decisione di autorizzazione da un JWT decodificato (non verificato). Non in un middleware, non in un hook React, non in una serverless function che pensi sia dietro il gateway. I claim decodificati dicono cosa il token afferma; i claim verificati dicono cosa l’issuer ha firmato. Un attaccante che consegna al tuo server un token costruito a mano senza una signature valida può scrivere qualsiasi cosa voglia nel payload; solo il controllo della signature lo rifiuta.

Per la famiglia HMAC: se usi HS256, l’entropia del tuo secret è tutto il gioco. Un secret corto e indovinabile viene bucato via brute-force offline contro qualsiasi token che l’attaccante intercetta; poi si conia da solo i token ed entra dalla porta principale. Usa almeno 256 bit di vera casualità. Vedi la nostra guida sulla forza del secret HMAC per l’aritmetica che spiega perché è importante.

Riferimento dei claim JWT più comuni

Ogni JWT che incontri usa un sottoinsieme dei claim registrati dall’RFC 7519. Impara a memoria la lista breve:

ClaimNomeEsempioNote
issIssuerhttps://auth.example.comChi ha emesso il token
subSubjectuser_123Di solito l’ID utente
audAudienceapi.example.comA chi è destinato il token; deve corrispondere sul server
expExpiration1715003600Secondi Unix; passato = scaduto
iatIssued At1715000000Secondi Unix in cui il token è stato emesso
nbfNot Before1715000060Primo istante in cui il token è utilizzabile
jtiJWT IDd1f8…Unico per token; previene il replay
kidKey ID (header)key-2025-01Quale chiave del tuo JWKS ha firmato questo

I claim specifici dell’applicazione vivono accanto a questi: role, scope, email, tenant_id, qualsiasi cosa il tuo identity provider emetta. Tienili corti: ogni byte viaggia insieme a ogni richiesta.

Per leggere date umane da iat ed exp, prova il nostro convertitore di timestamp Unix. Incolla il numero, ottieni la data nel tuo fuso orario locale, individua un bug di skew in un secondo.

Troubleshooting — perché il mio JWT non si decodifica?

Cinque vere modalità di errore, in ordine approssimativo di frequenza. Ciascuna è presentata come Sintomo → Causa → Soluzione.

  1. “Formato JWT non valido — attesi tre segmenti.” Hai copiato solo il payload, oppure la shell ha spezzato il token su più righe e hai preso solo la prima riga. Soluzione: ricopia l’intero valore xxx.yyy.zzz dal body della risposta originale, non da un terminale renderizzato. I valori lunghi su riga singola sopravvivono meglio nel tab Network dei devtools del browser che in un terminale scorrevole.
  2. Cinque segmenti invece di tre. Hai un JWE (JWT cifrato), non un JWS. Il formato è header.encryptedKey.iv.ciphertext.tag. Un decoder leggerà l’header ma il payload è testo cifrato. Soluzione: decodificare il payload richiede la chiave di decifratura, di solito gestita lato server dal tuo SDK di auth, non da uno strumento di debug.
  3. Errore base64url su un token che sembra per il resto valido. Il token è stato URL-encoded da qualche parte lungo il percorso di copia (un cookie, una URL di redirect, un log di proxy catturato). Vedrai %2E o %2B letterali nella stringa. Soluzione: URL-decodificalo prima, poi passa il risultato al decodificatore JWT.
  4. Errore di parsing JSON sul payload. Un terminale o un client di chat ha inserito newline di soft-wrap, oppure uno script ha incollato virgolette tipografiche attorno a un identificatore. Soluzione: visualizza i byte grezzi della risposta (curl con -o file.txt, o la vista Raw dei devtools), rimuovi gli spazi, incolla di nuovo.
  5. Si decodifica pulito ma il backend lo rifiuta lo stesso. Non è un problema di decodifica: è un problema di verifica. Il token è strutturalmente valido; qualcosa che il server controlla (signature, aud, exp, clock skew, lookup del kid) sta fallendo. Salta alla prossima sezione.

Due menzioni d’onore che non sono errori di parsing ma vale la pena intercettare mentre hai il decoder aperto: un valore alg uguale a none nell’header (trattalo come ostile in produzione) e un valore exp nel passato (il decoder mostra comunque i claim così puoi fare debug: è il comportamento corretto, e il nostro strumento lo segnala con un badge rosso Expired).

Quando la decodifica non basta — verifica della signature

La decodifica finisce a “ecco cosa il token afferma”. La verifica è ciò che trasforma un’affermazione in una decisione di fiducia. La signature è una prova, calcolata con la chiave privata o il secret condiviso dell’issuer, che lega header e payload: cambia un singolo byte e il controllo della signature fallisce. Senza quel controllo, chiunque possa fare POST al tuo endpoint può costruirsi a mano un token “admin” modificando il payload e saltando del tutto la signature.

Non accettare mai alg:none.

La verifica di produzione, in ogni linguaggio e framework, assomiglia grosso modo a questa checklist. Tratta le voci mancanti come bug:

  • Passa una allowlist esplicita algorithms: ['RS256'] (o qualunque tu usi). Questo sconfigge gli attacchi di algorithm-confusion.
  • Verifica che aud corrisponda all’identificatore del tuo servizio e che iss corrisponda all’URL dell’issuer atteso.
  • Controlla exp rispetto all’ora corrente con al massimo 60 secondi di tolleranza per lo skew.
  • In caso di rotazione della chiave, cerca la chiave pubblica tramite kid da un endpoint JWKS; non hardcodare mai una singola chiave per sempre.
  • Revoca in modo efficace tenendo exp corto (minuti, non giorni) e opzionalmente mantenendo una denylist jti per i token ad alto valore.

Ogni libreria JWT mainstream espone queste opzioni in una singola chiamata. Se il tuo codice di verifica non le imposta, stai lasciando i default attivi, e i default sono storicamente stati il bug. Per il threat model completo, vedi la nostra guida alle best practice di sicurezza.

FAQ

Come decodificare un JWT senza la chiave segreta?

Sì, puoi farlo. Header e payload sono codificati in base64url, non cifrati; chiunque possieda il token può leggerne i claim. Il secret o la chiave pubblica servono solo per verificare la signature. È una scelta progettuale: il payload è pensato per essere leggibile così che il destinatario possa prendere decisioni di autorizzazione.

È sicuro incollare il mio JWT di produzione in un decoder online?

Solo se il decoder gira nel tuo browser e non carica mai il token. Il nostro decoder JWT fa il parsing localmente con atob nativo e JSON.parse. Nulla viene inviato ad alcun server. I debugger remoti che fanno POST del tuo token a una API vanno trattati come fughe di credenziali.

Qual è la differenza tra decodificare e verificare un JWT?

Decodificare si limita a leggere i claim: non ha bisogno di alcuna chiave e non dimostra nulla. Verificare controlla la signature contro la chiave dell’issuer e conferma che il token non è stato manomesso. Non prendere mai una decisione di autenticazione da un token decodificato ma non verificato.

Il mio JWT sembra troncato — cosa conta come formato valido?

Un JWT valido ha esattamente tre segmenti base64url separati da punti: header.payload.signature. Cinque segmenti significano che hai un JWT cifrato (JWE), non un JWS. Zero punti significa che hai copiato un solo segmento da una riga di terminale spezzata.

Perché il decoder mi mostra comunque un token scaduto?

Un decoder legge i claim a prescindere dalla validità così puoi fare debug dei rifiuti. Solo un verifier rifiuta i token scaduti. Il nostro strumento fa emergere un badge Expired confrontando exp con il tuo orologio locale, così individui il problema all’istante senza strizzare gli occhi sui timestamp Unix.

Quali algoritmi posso decodificare?

Tutti. La decodifica ha bisogno solo di base64url e parsing JSON; è agnostica rispetto all’algoritmo. Ci sono inclusi HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA e alg:none. Solo la verifica dipende dall’algoritmo che hai scelto.

In Node.js dovrei usare jwt-decode o jsonwebtoken?

Usa jwt-decode sul frontend quando ti serve solo leggere il payload: per esempio, mostrare un username da un access token. Usa jsonwebtoken sul backend, perché solo il backend può detenere la chiave di firma ed eseguire jwt.verify. Non verificare mai sul client.

Conclusione

Decodificare un JWT è molto meno misterioso di quanto l’etichetta “token crittografico” lascerebbe intendere. Ricorda cinque punti e non ti blocchi più davanti a una stringa opaca eyJhbGciOi…:

  • Decodificare è base64url più JSON parse. Nessun secret richiesto.
  • Un JWT ha tre parti (header, payload, signature) unite da punti.
  • La decodifica non dimostra mai l’autenticità. Verifica sempre lato server con la chiave dell’issuer.
  • Rifiuta alg:none e passa sempre una allowlist esplicita di algoritmi a verify.
  • Non memorizzare mai password, chiavi private o PII sensibili nel payload: è leggibile da chiunque possieda il token.

Aggiungi ai preferiti il nostro decodificatore JWT gratuito per il debug in reperibilità. Incolla un token, leggi i claim, individua le scadenze in un secondo, il tutto senza che il tuo token lasci mai il browser.

Articoli correlati

Vedi tutti gli articoli