Basi della sicurezza
Supabase invalid JWT: quale chiave ha smesso di funzionare?
Un errore Supabase invalid JWT è PGRST301: un token firmato non ha superato la verifica. Su un progetto recente riguarda la sessione, non la chiave API.

In breve
- Un errore Supabase invalid JWT arriva come PGRST301 con HTTP 401, e significa che la firma del token partito con la tua richiesta non si è potuta verificare. Non è stato cercato nulla in un elenco per poi rifiutarlo.
- Le chiavi publishable e secret sono stringhe corte senza firma, quindi su un progetto che è passato a queste chiavi questo errore riguarda il tuo utente autenticato e mai la chiave API.
- PostgREST 13 ha sostituito una frase vaga con un breve elenco di frasi precise. Leggi il messaggio che hai ricevuto davvero: ognuno esclude una causa diversa.
Ieri la tua app funzionava. Stamattina ogni richiesta torna con un 401, nel tuo codice non è cambiato niente, e la risposta parla di un JWT, una cosa che non hai mai digitato da nessuna parte.
{
"code": "PGRST301",
"message": "JWT cryptographic operation failed"
}
Un errore Supabase invalid JWT è quello che si presenta dopo che hai fatto la cosa giusta. Hai ruotato una chiave perché te lo diceva una guida, oppure hai portato il progetto sulle chiavi nuove, e l'app è ammutolita.
Ed ecco il punto su cui guida dopo guida sbaglia: la chiave di cui parla questo
messaggio spesso non è quella che hai appena cambiato. Cerca l'errore e ti
manderanno a controllare la tua chiave anon. Su un progetto passato alle chiavi
publishable e secret, la chiave API non è affatto un token firmato, quindi ciò
che non ha superato la verifica era qualcos'altro nella richiesta.
Che cosa significa un errore Supabase invalid JWT?
Qualcosa nella tua richiesta era un token firmato, e la firma non corrispondeva.
Una password è una parola che qualcuno cerca in un elenco. Un JWT non è questo. È una lettera chiusa con un sigillo di ceralacca: la persona alla porta non ti cerca da nessuna parte, tiene in mano un timbro e controlla se il sigillo sulla tua lettera è stato fatto con quello. Se sì, passi, e nessuno legge nessun elenco.
Una lettera sigillata con il timbro sbagliato viene respinta anche se ogni parola al suo interno è giusta. E nel momento in cui la porta cambia timbro, nessuna lettera già in viaggio si apre più, senza che nessuno abbia toccato le lettere.
La tua richiesta è la lettera. Il tuo progetto Supabase tiene il timbro. Un 401 qui vuol dire che i due non coincidevano.
Il messaggio è cambiato in PostgREST 13, il codice no
Controlla PGRST301. La frase che lo accompagna dipende dalla versione di
PostgREST che gira sul tuo progetto, e quelle più recenti te ne danno un breve
elenco.
La referenza degli errori di PostgREST descrive PGRST301 come un JWT ricevuto
che non si è potuto decodificare o che non è valido, con HTTP 401, sia nella
versione 13 sia nella 14. Quella frase è la descrizione della tabella di
documentazione. Non è ciò che atterra nel tuo campo message. Leggendo
Error.hs sul branch main di PostgREST, quel campo porta una di queste:
| Che cosa dice il messaggio | Che cosa ti dice |
|---|---|
Empty JWT is sent in Authorization header | L'header è stato mandato ed era vuoto. |
Expected 3 parts in JWT; got 1 | Quello che è arrivato non è un JWT. Un JWT sono tre blocchi separati da punti. |
No suitable key or wrong key type | Il server non ha nessuna chiave in grado di verificare un token di quel tipo. |
Wrong or unsupported encoding algorithm | Il token indica un algoritmo di firma che il server non usa. |
JWT cryptographic operation failed | Il token è ben formato e la firma non corrisponde. |
Unsupported token type | Il token ha una forma che PostgREST non legge. |
JWT cryptographic operation failed è quello che vuol dire chiave vecchia. Il
token è stato costruito bene e firmato da qualcosa che il progetto non tiene più,
che è esattamente ciò che una rotazione lascia indietro.
I progetti più vecchi vedono tutt'altro, ed è da lì che arriva la stringa che si
trova ovunque nei risultati di ricerca. Prima della versione 13 PostgREST
stampava quello che aveva detto la sua libreria JWT, esattamente come l'aveva
detto. Da lì viene JWSError JWSInvalidSignature: è un valore di libreria
stampato a schermo, non una frase che qualcuno ha scritto per te. Il changelog di
PostgREST per la 13.0.0 descrive la modifica come messaggi di errore diagnostici
al posto di interni esposti.
Un codice vicino vale la pena conoscerlo mentre sei qui. PGRST303, anch'esso
401, è quello che ricevi quando la firma è stata verificata e il contenuto del
token è stato rifiutato: scaduto, non ancora valido, o con l'audience sbagliata.
Se è quello il tuo codice, smetti di guardare le chiavi e vai a guardare
l'orologio e la sessione.
Quale chiave è: la chiave API o il token del tuo utente?
Dipende da se il tuo progetto usa ancora anon e service_role.
La documentazione di Supabase sulle chiavi API è esplicita sulla differenza. Le
vecchie chiavi anon e service_role sono JWT di lunga durata, ed è per questo
che cominciano con eyJ. Le chiavi publishable e secret più recenti sono
stringhe corte e non sono affatto JWT.
Questo unico fatto taglia l'errore in due:
- Il tuo progetto usa ancora
anoneservice_role. La chiave API della tua richiesta è essa stessa un token firmato, quindi una chiave di un altro progetto, una chiave di prima di una rotazione o un incolla troncato falliscono la verifica della firma esattamente come farebbe una sessione sbagliata. Entrambi i token della richiesta possono essere quello fallito. - Il tuo progetto è passato a
sb_publishable_esb_secret_. La chiave API ha smesso di essere un token, quindi non c'è nessuna firma su di essa che qualcosa possa rifiutare. Resta da verificare l'access token del tuo utente autenticato, che Supabase Auth emette separatamente dalla chiave API.
In pratica, la prima domanda non è quale chiave sia sbagliata. È se qualcuno fosse autenticato quando la richiesta è fallita. Se una pagina senza accesso funziona e una con accesso no, stai guardando le sessioni. Se fallisce tutto, inclusa una pagina che non chiede niente di personale, stai guardando la chiave API, e il tuo progetto è di quelli vecchi.
Se sono le sessioni e il tuo log di Auth dice anche Invalid Refresh Token, i
tuoi utenti vengono disconnessi perché
la sessione dietro il loro token è finita.
Se la risposta arriva da Supabase Auth e dice invalid claim: missing sub claim, il token ha passato il controllo della firma e non nominava nessuno. È
una chiave mandata dove va il token di una persona, e
quel messaggio ha un articolo tutto suo.
Invalid JWT non è "No API key found in request"
Sono due messaggi diversi con due cause diverse, e le correzioni non hanno niente in comune.
Un invalid JWT vuol dire che un token è arrivato e la firma non corrispondeva.
"No API key found in request" vuol dire che
non è arrivato nessun header apikey, quindi la richiesta è stata respinta al
gateway prima che al tuo database venisse chiesto qualcosa.
È la risposta stessa a dirti quale dei due hai. Una chiave mancante viene
respinta dal gateway davanti al tuo progetto, quindi il corpo porta un message
e un hint e nessun code. Un invalid JWT è arrivato abbastanza avanti da
essere letto da PostgREST, ed è per questo che torna con scritto PGRST301.
Dove sta ancora la vecchia chiave, in ordine
Quattro posti, e conviene controllarli in quest'ordine perché è grosso modo la frequenza con cui ognuno è la risposta.
- Le impostazioni di progetto del tuo strumento. Lovable, Bolt, v0 e Replit tengono ciascuno il proprio set di variabili d'ambiente, e se hai costruito con Cursor o Claude Code lo stesso set sta dove fai il deploy. È la copia che quasi tutti aggiornano, e l'unica che quasi tutti aggiornano.
- Una chiave scritta direttamente nel codice. Viaggia dentro il JavaScript che scaricano i tuoi visitatori, quindi cambiarla nella dashboard non cambia niente finché il file non viene modificato e il sito ricostruito. Dove sta quella pagina e cosa fa ogni valore, se non l'hai mai aperta.
- I secret delle Edge Function. Una funzione tiene il proprio set, separato da quello del tuo frontend, e continua quindi a usare la vecchia chiave quando tutto il resto si è già spostato. Supabase avverte anche che una funzione con l'impostazione Verify JWT attiva può rompersi quando ruoti una chiave di firma.
- Un deploy che nessuno ha ricostruito. La chiave è stata incorporata all'ultima build, quindi la copia online continua a mandarla finché qualcosa non innesca una build nuova. La tua dashboard mostra la chiave nuova per tutto il tempo, ed è questo che rende difficile vedere questo caso: il posto dove vai a controllare è l'unico che è già a posto.
Quale chiave manda davvero la tua app in produzione
Apri il sito pubblicato invece dell'anteprima, e leggi cosa manda la pagina.
Una vecchia chiave Supabase è una stringa lunga che comincia con eyJ. Quelle
più recenti si annunciano nei primi caratteri: sb_publishable_ sta nella tua
app e sb_secret_ mai. Se trovi la seconda in una pagina che un visitatore può
caricare, lascia perdere questo articolo e
ruotala prima di qualsiasi altra cosa.
Quale delle due hai in mano, e
che cosa è cambiato quando Supabase le ha
rinominate, sono le cose da chiarire prima di
cominciare a sostituire valori.
Per vedere cosa emette il tuo progetto adesso: tutte le chiavi stanno ormai in un posto solo nella dashboard, sotto Settings e poi API Keys, comprese quelle vecchie.
Se preferisci non leggere un bundle minificato, la nostra scansione gratuita legge la tua app in produzione come la leggerebbe uno sconosciuto e segnala le chiavi visibili dall'esterno. Ci mette una ventina di secondi e non serve un account: scansiona la tua app.
Ruotare una chiave di firma senza disconnettere tutti
Quale dei due percorsi di rotazione stai seguendo decide se i tuoi utenti se ne accorgono.
La documentazione di Supabase sulle chiavi di firma li mette uno accanto
all'altro. Ruotare il vecchio secret JWT disconnette subito tutti gli utenti
attivi, e anon e service_role vanno ruotate nello stesso momento. Le JWT
signing keys più recenti funzionano diversamente: una chiave nuova parte come
standby, la rotazione la rende quella corrente, e quella che sostituisce diventa
la precedente. Finché è così, le firme di entrambe le chiavi continuano a essere
accettate, e le lettere sigillate la settimana scorsa si aprono ancora. Revocare
la vecchia chiave è il passo separato che mette fine a tutto questo.
Due avvertenze nella stessa documentazione sono quelle che fregano:
- Un componente che verifica i token per conto proprio contro il vecchio secret JWT, con una libreria come jose o jsonwebtoken, si rompe alla rotazione. Tiene in mano il vecchio timbro e nessuno gli ha detto di cambiarlo.
- Una Edge Function con Verify JWT attivo può rompersi per lo stesso motivo.
Creare chiavi publishable e secret non revoca nemmeno le tue chiavi vecchie. Continuano a funzionare finché non le disattivi, cosa che la documentazione segnala come un passo a parte. Così un'app che ha spostato solo metà delle sue copie continua a funzionare per settimane, e il giorno in cui qualcuno disattiva la vecchia coppia è il giorno in cui l'altra metà comincia a rispondere 401.
Tenere d'occhio un'app che ha cominciato a rispondere 401
Un'app che risponde 401 a ogni richiesta è comunque online, continua a servire una pagina e continua a rispondere a qualsiasi controllo di salute. Nella tua dashboard non diventa rosso niente. Lo scopri quando qualcuno ti scrive.
Reeve Monitor lo scopre per primo:
- tutti e nove i controlli ogni ora, su un massimo di tre app
- un'email il giorno in cui una nuova scansione valuta la tua app peggio della precedente
- se l'app risponde, ogni 60 secondi
- un rapporto mensile di quello che ha visto
Una chiave che hai dovuto ruotare in fretta di solito è una chiave che stava dove non doveva, e da qui non puoi vedere cosa sia stato letto o modificato con essa mentre era fuori. Reeve Care tiene una copia del tuo database Supabase di prima.
- una copia cifrata secondo un calendario, tenuta sullo storage di Reeve nell'UE, fuori dal tuo account Supabase
- ogni copia verificata prima di contare, contando le righe di ogni tabella
- un ripristino in un clic che salva quello che c'è ora prima di sostituirlo
- anche i tuoi file caricati, appena colleghi un accesso Storage
- tutto quello che fa Monitor
Cosa fare oggi
Cosa fare
- Leggi il campo
code, non la frase.PGRST301è una firma che non corrispondeva,PGRST303è un token il cui contenuto è stato rifiutato, per esempio scaduto. - Capisci quale token è fallito prima di cambiare una chiave. Se le pagine senza accesso funzionano e quelle con accesso no, è una sessione e non la tua chiave API.
- Controlla tutti e quattro i posti dove si nasconde la vecchia chiave: le impostazioni dello strumento, il codice stesso, i secret delle Edge Function e un deploy che nessuno ha ricostruito.
- Fai la rotazione in una sola sessione. Ogni copia che lasci indietro è un'app che funziona a metà, e il sito online continua a mandare la vecchia chiave finché non viene ricostruito.
- Con le nuove chiavi di firma è la revoca il passo che chiude davvero le vecchie firme. La sola rotazione le lascia accettate.
Comincia chiedendoti se una pagina senza accesso si carica ancora, perché quella sola risposta decide quale metà di questo articolo ti riguarda. Se salta fuori che una chiave secret era allo scoperto, ruotarla copre l'ordine in cui procedere, e quali chiavi sono mai state sicure da pubblicare racconta perché le due sono finite per assomigliarsi tanto.
FAQ
Che cosa significa PGRST301?
PGRST301 è PostgREST che risponde HTTP 401 perché non è riuscito a verificare un token firmato presente nella tua richiesta. PostgREST è il server che trasforma le tue tabelle Supabase nell'API HTTP con cui parla la tua applicazione. La sua stessa referenza degli errori descrive il codice come un JWT ricevuto che non si è potuto decodificare o che non è valido, e dalla versione 13 il campo message porta una frase più precisa su quale parte della verifica è fallita.
Perché ricevo 401 dopo aver ruotato la mia chiave Supabase?
Perché da qualche parte viene ancora inviata una copia della vecchia chiave. I posti soliti sono le impostazioni di progetto del tuo strumento, una chiave scritta direttamente nel codice, un secret di Edge Function e un deploy costruito prima della modifica e mai ricostruito da allora. Un token firmato con una chiave che il server non accetta più non supera la verifica, e questo ti arriva come 401 su ogni richiesta invece che come un messaggio sulla chiave.
Che differenza c'è con No API key found in request?
Sono due messaggi diversi con cause diverse. No API key found in request significa che la richiesta è arrivata senza alcun header apikey ed è stata respinta al gateway prima che al database venisse chiesto qualcosa. Un invalid JWT significa che un token c'era e la sua firma non corrispondeva. Il primo è una lettera senza indirizzo, il secondo una lettera con il sigillo sbagliato.
Devo aggiornare anche le mie Edge Functions?
Sì, ed è la copia che si dimentica più spesso, perché una Edge Function tiene i propri secret, separati da quelli che usa il tuo frontend. Supabase avverte anche che una funzione con l'impostazione Verify JWT attiva può rompersi quando ruoti una chiave di firma, e che qualunque componente che verifichi i token per conto proprio contro il vecchio secret JWT si romperà per lo stesso motivo.
Come vedo quale chiave manda davvero la mia app in produzione?
Apri il sito pubblicato, non l'anteprima, e guarda che cosa manda la pagina davvero. Una vecchia chiave Supabase è una stringa lunga che comincia con eyJ, e quelle più recenti cominciano con sb_publishable_ o sb_secret_. La nostra scansione gratuita legge un'app in produzione come la leggerebbe uno sconosciuto e segnala le chiavi visibili dall'esterno, il che dice anche se ne viene ancora mandata una vecchia.