Vai al contenuto

Basi della sicurezza

Supabase "Auth session missing!": dove è finita la sessione

"Auth session missing!" è supabase-js che non trova nessuna sessione e non arriva nemmeno a chiedere a Supabase. Dove è finita la sessione, e la correzione.

Vlad Tkachenko11 min di lettura
Un porta-badge vuoto appeso a un cordino, con lo spazio del badge contornato in ambra, davanti a due badge che hanno un volto.

In breve

  • "Auth session missing!" (AuthSessionMissingError, stato 400) viene da supabase-js, dentro la tua app. La libreria ha cercato una sessione con login, non ne ha trovata nessuna e non ha mai mandato la richiesta a Supabase.
  • Su una pagina dove nessuno ha fatto login è la risposta giusta, e il tuo codice deve solo gestirla. Su una pagina di reimpostazione della password vuol dire che il link non ha fatto entrare nessuno qui. In una Edge Function o su un server, la sessione del visitatore non è mai stata passata.
  • Lascia getUser() dove sta. Sostituirlo con getSession() su un server fa sparire l'errore credendo a qualunque cosa dica il cookie.

La tua app Lovable o Bolt fa entrare le persone con Supabase. Una mattina apri la console del browser e trovi questo in rosso, su una pagina dove niente sembra sbagliato:

AuthSessionMissingError: Auth session missing!

Oppure una cliente ti scrive che la pagina "Scegli una nuova password" la respinge con queste stesse parole, ogni volta.

Cerca "auth session missing supabase" e troverai discussioni piene di correzioni: sostituire getUser() con getSession(), tornare a una versione precedente della libreria, aggiungere un'attesa, riavviare il progetto. Danno per scontato che qualcosa si sia rotto tra la tua app e Supabase. Non è partita nessuna richiesta. L'errore nasce dentro la tua app, scritto dalla libreria di Supabase, prima che qualsiasi cosa lasci il browser.

Che cosa significa "Auth session missing!" in Supabase?

A supabase-js è stato chiesto chi ha fatto login; ha cercato una sessione, non ne ha trovata nessuna e ha risposto da sola.

Una sessione è quello che Supabase Auth consegna a una persona quando fa login: una coppia di token che dicono chi è, tenuti dal browser perché il caricamento di pagina successivo la riconosca ancora. supabase-js è la libreria con cui la tua app parla con Supabase, e ogni chiamata che ha bisogno di una persona comincia tirando fuori quella sessione.

Il messaggio è scritto in supabase-js stessa, nel file errors.ts della libreria di autenticazione: un AuthSessionMissingError con il testo Auth session missing! e lo stato 400. Quel 400 è un'etichetta che la libreria si dà da sola. Nessun server ha risposto così, e i log di Supabase non mostreranno nulla, perché getUser() cerca una sessione prima di fare qualsiasi altra cosa. Un commento nel suo codice lo dice: restituisce un errore se non c'è né un token di accesso né un header Authorization personalizzato.

Immagina una reception capace di dirti chi è ogni visitatore. A chiunque faccia login viene dato un badge all'ingresso, e supabase-js tiene quel badge nella tasca del browser. Quando il tuo codice chiede "chi è questo?", supabase-js guarda prima nella tasca. Se c'è un badge, lo porta alla reception e torna con un nome. Se la tasca è vuota, alla reception non ci va nemmeno. Torna subito e ti dice che il badge non c'è.

Con una sessione, supabase-js chiede a Supabase Auth. Senza, si ferma dentro la tua app, e l'errore viene da lì.

Viene restituito, non lanciato

getUser() restituisce l'errore accanto a un utente vuoto, ed è il tuo codice a decidere che cosa succede dopo.

La chiamata è questa, e senza sessione user è null ed error contiene l'AuthSessionMissingError:

const { data: { user }, error } = await supabase.auth.getUser()

Non viene lanciato niente, quindi un try … catch attorno alla chiamata non cattura niente. È da qui che viene il danno visibile, in una di due forme:

  • Il codice mostra qualsiasi error riceva. Un visitatore che non ha ancora fatto login vede "Auth session missing!" in una notifica o in un riquadro rosso, su una pagina che poteva vedere.
  • Il codice usa l'utente senza controllare. Legge user.id e la pagina si rompe con Cannot read properties of null, che sembra tutt'altro bug.

Sono lo stesso errore: trattare "nessuno ha fatto login" come un guasto. Controlla prima l'utente, e dai a un visitatore senza login la schermata pensata per lui:

const { data: { user } } = await supabase.auth.getUser()
if (!user) {
  // nessuno ha fatto login: mostrare la schermata di accesso o la pagina pubblica
  return
}

Se vuoi distinguere questo caso da un guasto vero, la libreria gli dà un nome: error.name è 'AuthSessionMissingError'.

Dove si perde la sessione

In tre posti, e solo il primo è innocuo.

1. In questo browser nessuno ha fatto login. La tua app chiama getUser() su una pagina che chiunque può aprire, magari per decidere se mostrare "Accedi" o "Il mio account". Per un visitatore, la tasca vuota è la risposta vera. È qui che la maggior parte delle persone incontra il messaggio, e la correzione del codice qui sopra è tutto ciò che serve.

2. Una pagina di reimpostazione della password o di magic link. Scegliere una nuova password chiama updateUser({ password }), e la guida alle password di Supabase è chiara sull'ordine: è una volta ottenuta la sessione che si raccoglie la nuova password e si chiama updateUser. Su quella pagina, la sessione deve arrivare dal link dell'email, che fa entrare la persona mentre si apre. Quando non lo fa, la tasca è vuota e updateUser() risponde "Auth session missing!". I motivi soliti:

  • Il link era già stato usato, o era scaduto, prima di essere aperto. Perché Supabase dice che il link dell'email non è valido o è scaduto li passa in rassegna uno per uno.
  • È stato aperto in un browser o in un'app diversi da quelli che l'hanno chiesto. Quando un'app usa il login PKCE di Supabase, una parte dello scambio resta nel browser che l'ha cominciato, e secondo la documentazione di Supabase lo scambio del codice va avviato nello stesso browser e sullo stesso dispositivo in cui è cominciato il flusso.
  • Ha mandato la persona altrove, per esempio su localhost, così la sessione è arrivata a un indirizzo che non è la tua app. Perché Supabase reindirizza a localhost copre questo caso.

3. Il tuo server o una Edge Function non ha mai ricevuto la sessione. Il badge è nel browser del tuo visitatore. Il codice che gira altrove, una Edge Function di Supabase o la metà server di un'app Next.js fatta con v0 o Cursor, crea il proprio client Supabase, e un client nuovo comincia con la tasca vuota. Finché la sessione del visitatore non gli viene passata, getUser() lì non trova niente e risponde "Auth session missing!" senza chiedere a Supabase.

In una Edge Function la sessione viaggia nell'header Authorization della richiesta, e supabase-js la usa solo se gliela consegni quando crei il client. In un'app Next.js viaggia nei cookie, che lo strumento server di Supabase legge solo se gli dai quelli della richiesta. Se una Edge Function restituisce quel fallimento come errore, la tua app mostra "Edge Function returned a non-2xx status code", con questo messaggio che aspetta nel log della funzione.

La sessione appartiene al browser. Una funzione o un server ce l'ha solo se la richiesta la porta e il codice la passa al suo client.

Come capire qual è il tuo caso

Guarda dove compare il messaggio, e poi che cosa ha mandato davvero il browser.

Nella console del browser, su una pagina che chiunque può aprire. Caso 1. Apri gli strumenti per sviluppatori del browser (F12), scegli Application, poi Local Storage e l'indirizzo della tua app. supabase-js tiene la sessione sotto una chiave fatta da sb-, l'identificativo del tuo progetto e -auth-token. Se non c'è una voce del genere, questo browser non ha una sessione, e il messaggio dice il vero. Un'app Next.js che usa gli strumenti server di Supabase tiene la sessione nei cookie, quindi cerca lo stesso nome in Cookies.

Su una pagina di reimpostazione o di magic link. Caso 2. Guarda l'indirizzo su cui si è aperto il link. Se non è la tua app, o se è un link che tu o uno scanner della posta avevate già aperto, la sessione non ha mai avuto modo di arrivare.

Nel log di una Edge Function, o del tuo server. Caso 3. Apri la scheda Network, ripeti l'azione e fai clic sulla richiesta alla tua funzione. Se il suo header Authorization contiene una lunga stringa che comincia con eyJ, il browser ha mandato il token di una persona, e la tua funzione non l'ha passato al suo client.

Quello che non troverai su nessuna strada è una richiesta a /auth/v1/user tornata con questo messaggio. Se ne vedi una fallita, è un altro errore. Una risposta che dice invalid claim: missing sub claim vuol dire che la chiave del tuo progetto è stata mandata dove doveva esserci una persona, e quell'articolo è il suo gemello.

La correzione per ogni caso

Dai a ciascuno la sessione che gli manca, o una risposta che non ne abbia bisogno.

Caso 1, il visitatore senza login: controlla user prima di usarlo, come nel codice qui sopra, e mostra la schermata pensata per chi non ha fatto login.

Caso 2, la pagina di reimpostazione: aspetta che il link abbia fatto entrare la persona prima di mostrare il modulo. Supabase emette un evento PASSWORD_RECOVERY quando qualcuno arriva da un link di recupero, e il suo riferimento mostra la pagina che lo aspetta:

supabase.auth.onAuthStateChange((event) => {
  if (event === 'PASSWORD_RECOVERY') {
    // il link ha fatto entrare la persona: ora mostrare il modulo della nuova password
  }
})

Se l'evento non arriva mai, il link non ha funzionato. Dillo e offri di mandarne uno nuovo, invece di mostrare un modulo destinato a fallire.

Caso 3, la funzione o il server: passa la sessione del visitatore al client che crei. In una Edge Function, crealo con l'header in arrivo:

const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_ANON_KEY')!,
  { global: { headers: { Authorization: req.headers.get('Authorization')! } } },
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
  return new Response(JSON.stringify({ error: 'Sign in first' }), {
    status: 401,
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
}

Il wrapper più recente di Supabase, withSupabase({ auth: 'user' }), fa la stessa cosa prima che parta il tuo handler, e l'articolo sulle Edge Function mostra come impostarlo. Tieni il 401 per chi chiama senza sessione, così una richiesta da una pagina senza login riceve un rifiuto chiaro. Con l'header passato, getUser() lì può restituire invece invalid claim: missing sub claim per chi chiama senza login: lo stesso visitatore visto dall'altra parte, e il 401 copre entrambi i casi.

In un'app Next.js, crea il client server con lo strumento @supabase/ssr di Supabase e i cookie della richiesta, come mostra la sua configurazione lato server, e chiama getUser() su quel client.

Due correzioni che peggiorano le cose

Vengono suggerite entrambe per questo messaggio, e ciascuna ferma l'errore togliendo il controllo che c'è dietro.

Sostituire getUser() con getSession(). Nel browser non cambia niente di importante: lo storage è ancora vuoto, quindi non ottieni né sessione né utente, solo senza il messaggio. Su un server è pericoloso. La guida lato server di Supabase dice di non fidarsi mai di supabase.auth.getSession() nel codice server, perché legge la sessione dal cookie senza riconvalidarla. Un cookie è qualcosa che ti manda il visitatore, e chiunque può mandarti il cookie che vuole. getUser() è il metodo che supabase-js documenta come una richiesta al server di Supabase Auth, quindi il suo risultato è autentico. Tienilo ovunque la risposta decida che cosa qualcuno può fare.

Mettere la chiave segreta nel client perché la chiamata smetta di fallire. Un client creato con la chiave service_role o sb_secret_ non ha bisogno di una sessione per leggere e scrivere, perché scavalca del tutto le regole del tuo database. La funzione torna a funzionare, per chiunque la chiami, con login o senza. Se un builder fa questa modifica in codice che finisce nel browser, la chiave parte con lui verso ogni visitatore. Se è già successo, ruotala per prima cosa.

Lo puoi verificare da fuori. La nostra scansione gratuita legge il JavaScript della tua app pubblicata come farebbe un estraneo e nomina ogni chiave che trova, compreso se è una di quelle che lì ci devono stare. Ci mette circa 20 secondi e non chiede un account: scansiona la tua app.

Tenere il controllo al suo posto dopo la prossima modifica

Il controllo su chi ha fatto login è la parte di una pagina che più facilmente sparisce quando chiedi al tuo builder di far sparire un errore, ed entrambe le correzioni rapide qui sopra lo tolgono.

Reeve Monitor scansiona di nuovo la tua app pubblicata ogni ora:

  • tutti e nove i controlli ogni ora, su un massimo di tre app
  • una email il giorno in cui una nuova scansione valuta la tua app peggio della precedente; una chiave segreta nel tuo JavaScript limita da sola il voto a D
  • se l'app risponde, ogni 60 secondi
  • un rapporto mensile di quello che ha visto

Un server che crede a qualsiasi cookie, o una funzione che gira con la chiave segreta, lascia che un estraneo cambi le righe dei tuoi utenti. Reeve Care tiene una copia del tuo database Supabase di prima che succeda.

  • una copia cifrata secondo una pianificazione, conservata 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'è adesso prima di sostituirlo
  • anche i tuoi file caricati, appena colleghi una credenziale di Storage
  • tutto quello che fa Monitor

Che cosa fare adesso

Cosa fare

  • Leggi "Auth session missing!" come "qui nessuno ha fatto login". La richiesta non ha mai lasciato la tua app.
  • Controlla se user è null prima di usarlo, e dai a un visitatore senza login la schermata pensata per lui invece dell'errore.
  • Su una pagina di reimpostazione, aspetta l'evento PASSWORD_RECOVERY prima di mostrare il modulo, e offri un nuovo link se non arriva mai.
  • In una Edge Function o su un server, passa l'header Authorization o i cookie del visitatore al client che crei, e rispondi 401 se l'utente continua a mancare.
  • Tieni getUser() sul server. Lì getSession() crede a qualsiasi cookie gli venga mandato.

Se le tue sessioni esistono e poi spariscono, e persone che avevano fatto login si ritrovano fuori all'improvviso, è un altro problema con cause sue, e perché gli utenti Supabase vengono disconnessi lo tratta. La checklist di sicurezza in 10 minuti copre che cos'altro un'app appena lanciata lascia spesso aperto.

FAQ

Che cosa significa "Auth session missing!" in Supabase?

Significa che a supabase-js, la libreria di Supabase dentro la tua app, è stato chiesto l'utente con login e non aveva nessuna sessione con cui chiederlo. Una sessione è quello che Supabase Auth consegna a una persona quando fa login, e la libreria la tiene nel browser. Se non ce n'è nessuna, non contatta Supabase per niente. Restituisce AuthSessionMissingError con lo stato 400 e un utente null.

Perché vedo "Auth session missing!" quando nessuno ha fatto login?

Perché è la risposta per un visitatore che non ha fatto login. La tua app ha chiamato getUser() su una pagina che chiunque può aprire, e supabase-js ha segnalato che nessuno aveva fatto login. Non c'è niente di rotto. Il tuo codice deve solo leggere l'utente come null e mostrare la schermata di accesso, invece di mostrare l'errore.

Perché la mia pagina di reimpostazione della password dice "Auth session missing!"?

Cambiare una password con updateUser() richiede una sessione, e su quella pagina la sessione deve arrivare dal link dell'email. Se il link era già stato usato o era scaduto, è stato aperto in un browser diverso da quello che l'ha chiesto, o ha mandato la persona a un altro indirizzo, nessuna sessione è arrivata e updateUser() non ha nessuno da modificare. Aspetta l'evento PASSWORD_RECOVERY prima di mostrare il modulo, e offri un nuovo link se non arriva mai.

È la stessa cosa di "invalid claim: missing sub claim"?

No. "Auth session missing!" viene deciso dentro la tua app, prima che qualsiasi richiesta ne esca. "invalid claim: missing sub claim" torna da Supabase Auth, che ha ricevuto una richiesta e ha trovato la chiave del tuo progetto dove doveva esserci il token di una persona. Il primo vuol dire che non c'era niente da mandare. Il secondo, che è stata mandata la cosa sbagliata.

Devo usare getSession() invece di getUser()?

Non per far sparire questo errore. Nel browser, getSession() trova lo stesso spazio vuoto e non restituisce nessuna sessione, quindi non cambia niente tranne che il messaggio smette. Su un server è peggio: supabase-js documenta che getSession() legge la sessione direttamente dallo storage, per esempio dai cookie della richiesta, che possono non essere autentici, e raccomanda getUser() al suo posto perché verifica con Supabase Auth.

Scritto da

Vlad Tkachenko

Fondatore di Reeve

Passo le giornate a guardare app costruite con Lovable, Bolt, v0, Cursor e Replit, e la breve lista di errori che vi ricompaiono di continuo.

Altro sull'autore

Da leggere dopo

Tutti gli articoli

Fai il backup del database Supabase prima della prossima modifica

Reeve ne tiene una copia cifrata a intervalli programmati e la ripristina per te se qualcosa va storto.

Vedi i backup Supabase

Controllo esterno automatizzato, non un audit completo. L'assenza di risultati non è una garanzia di sicurezza.