Basi della sicurezza
"Invalid login credentials" in Supabase: le tre cause
Supabase dice "Invalid login credentials" per una password sbagliata, un'email sconosciuta e un account Google. Come capire quale.

In breve
- Supabase risponde "Invalid login credentials" a tre persone diverse: chi ha scritto la password sbagliata, chi ha un'email senza account e chi si è registrato con Google e non ha mai avuto una password.
- Lo fa apposta. Un modulo di accesso che dicesse quale dei tre casi è stato direbbe a chiunque abbia una lista di indirizzi email quali appartengono ai tuoi utenti.
- Puoi distinguere i tre da solo con una query su auth.users. La tua pagina di accesso dovrebbe tenere il messaggio vago e offrire ogni modo in cui l'account può essere stato creato.
Qualcuno ti scrive che non riesce ad accedere alla tua app ed è sicuro che la password sia giusta. Provi tu stesso il suo indirizzo e ottieni la stessa riga rossa sotto il modulo:
Invalid login credentials
Supabase risponde "Invalid login credentials" a una password sbagliata. E risponde con le stesse identiche parole ad altre due persone: chi ha un'email senza account nella tua app e chi si è registrato con Google e non ha mai avuto una password. La tua app fatta con Lovable, Bolt, v0, Cursor, Claude Code o Replit funziona come è stata costruita. Dà una sola risposta a tre situazioni diverse.
Ecco la parte che la correzione ovvia sbaglia: il messaggio vago è voluto, e renderlo più utile apre una falla. Quello che aiuta è capire quale dei tre casi hai davanti, cosa che la tua dashboard può dirti, mentre il modulo continua a dire poco come adesso.
Che cosa significa "Invalid login credentials" in Supabase?
Significa che Supabase Auth, la parte di Supabase che fa accedere le persone, ha rifiutato un'email e una password e tiene per sé il motivo.
Il messaggio è scritto nel codice stesso del server di Auth. Nella
funzione che gestisce un accesso con password,
lo stesso errore viene restituito in tre punti: quando nessun utente ha
quell'email, quando l'utente non ha una password salvata e quando la password
non corrisponde. Ogni volta la risposta è un HTTP 400 con il codice
invalid_credentials, che il
riferimento dei codici di errore
di Supabase descrive come "Login credentials or grant type not recognized."
Nella tua app arriva come AuthApiError da signInWithPassword, con
error.code uguale a invalid_credentials. Lo stesso riferimento chiede di
riconoscere gli errori sempre da error.code ed error.name, e questo conta più
avanti, quando decidi cosa dice il tuo modulo. Se i tuoi utenti erano dentro e
poi sono stati buttati fuori, è un altro errore, con
una pagina tutta sua.
Pensa al tuo modulo di accesso come al tastierino accanto al portone di un palazzo. Qualcuno digita un codice e una spia risponde. La verde apre la porta. La rossa si accende per un codice sbagliato, per un codice mai dato a nessuno e per un residente che è sempre entrato dalla porta laterale con un badge e non ha mai avuto un codice.
Le tre persone dietro il messaggio
Ognuna richiede una correzione diversa, quindi conviene sapere chi hai davanti.
La password sbagliata. L'account esiste, ha una password, e quello che è stato digitato non corrisponde. Ci rientrano una password cambiata su un altro dispositivo e poi dimenticata, e un gestore di password che ne inserisce una vecchia.
L'indirizzo senza account. Nessuno nel tuo progetto si è registrato con quell'email. Può essere un secondo indirizzo (quello di lavoro, quando la persona si è registrata con quello personale) o un errore di battitura alla registrazione, ora salvato come sua email.
L'account senza password. La persona si è registrata con Google, GitHub o un altro pulsante di accesso, e Supabase non ha mai salvato una password per lei. Quando torna e ne digita una, non c'è niente con cui confrontarla. Il server di Auth controlla che ci sia una password salvata prima di guardare quella digitata, e un account senza riceve lo stesso 400.
Chi si è registrato con un magic link, il link di accesso inviato per email, è una variante del primo caso. Il codice dei magic link di Supabase crea l'account con una password casuale che non viene mai mostrata a nessuno, quindi qualsiasi password la persona digiti è sbagliata.
Come capisco quale dei tre casi è?
Chiedilo al tuo database. Ogni account del tuo progetto è una riga in una
tabella chiamata auth.users, e quella riga dice come accede la persona.
La guida sugli utenti di Supabase
descrive il campo che conta: secondo Supabase, app_metadata.providers è
l'elenco dei provider con cui l'utente può accedere. Un utente può avere più
identità, quindi la stessa persona può avere sia una password sia Google. Se una
password è salvata lo dice la colonna che il server stesso controlla.
Apri il SQL Editor nella dashboard di Supabase ed esegui questo con l'indirizzo che ti ha dato la persona:
select email,
raw_app_meta_data->'providers' as signs_in_with,
coalesce(encrypted_password, '') <> '' as has_password,
email_confirmed_at
from auth.users
where lower(email) = lower('person@example.com');
Legge una riga e non mostra mai una password, solo se ne esiste una.
| Cosa torna | Cosa significa |
|---|---|
| Nessuna riga | Nessun account ha quell'indirizzo in questo progetto. Chiedi quale altro indirizzo potrebbe aver usato |
has_password è false, e signs_in_with contiene google, github o simili | Si è registrata con quel pulsante e non ha mai avuto una password |
has_password è true | La password digitata è sbagliata, oppure si è registrata con un magic link e non ha mai saputo la sua |
email_confirmed_at è vuoto | Non ha ancora confermato l'email, il che diventa un altro messaggio quando la password è giusta |
Se preferisci cliccare invece di scrivere, Authentication → Users nella dashboard elenca gli stessi account.
L'account che non ha mai avuto una password
Rimanda la persona al pulsante che ha usato, oppure lasciale impostare una password con il tuo link di reimpostazione.
Al tastierino, è il residente che ha sempre usato la porta laterale. Per lui non c'è alcun codice registrato, quindi ogni codice che prova viene rifiutato.
La prima riparazione è sulla tua pagina di accesso. Se la registrazione offre "Continua con Google", la pagina di accesso ha bisogno dello stesso pulsante, così chi si è registrato con quello lo vede quando torna.
Se la persona vuole anche una password, il tuo link "Password dimenticata?" lo
fa già. Il
codice di reimpostazione della password
di Supabase invia l'email di recupero a qualsiasi account trovi, senza
controllare come è stato creato. Un utente Google che segue il link e imposta una
nuova password può poi accedere in entrambi i modi. Questo dipende dal fatto che
la tua app abbia la pagina che imposta la nuova password, quella che chiama
supabase.auth.updateUser({ password }). Se il link porta a una pagina che non
la chiede mai, la persona entra una volta e resta senza password.
"Email not confirmed" vuol dire che la password era giusta
È un altro messaggio con un altro codice, e arriva solo a chi ha già superato il controllo della password.
Nella stessa funzione del server di Auth, il controllo dell'email confermata
viene dopo quello della password. Quindi Email not confirmed, codice
email_not_confirmed, torna solo a chi ha digitato un'email esistente e la sua
password corretta. Il riferimento degli errori di Supabase lo formula come
"Signing in is not allowed for this user as the email address is not
confirmed." Al tastierino è un'altra spia: il codice era giusto, e la porta
resta chiusa finché le pratiche del trasloco non sono finite.
Lo vedi quando Confirm email è attivo per il provider Email del tuo
progetto. Secondo la
guida alla configurazione
di Supabase, gli utenti devono allora confermare il loro indirizzo email prima
di accedere per la prima volta. La correzione è l'email su cui la persona non ha
ancora cliccato: dille di controllare la posta in arrivo e lo spam, e offri di
inviarla di nuovo con supabase.auth.resend({ type: 'signup', email }). Se
quelle email non arrivano proprio,
il limite di invio delle email è la
prima cosa da escludere. Se arrivano e il link risponde "Email link is invalid
or has expired",
probabilmente qualcosa l'ha aperto prima della persona.
Visto che questo messaggio compare solo dopo una password giusta, dire alla persona di controllare la posta non dà a uno sconosciuto niente che non avesse già.
Perché il messaggio resta vago
Perché la versione utile direbbe a degli sconosciuti chi sono i tuoi utenti.
Immagina che il tastierino rispondesse "nell'interno 4B non abita nessuno". Chiunque passi potrebbe scorrere l'elenco degli appartamenti e sapere quali sono abitati.
Il tuo modulo funziona allo stesso modo. Se dicesse "Nessun account con questa email", chiunque abbia una lista di indirizzi potrebbe digitarli uno per uno e sapere quali appartengono ai tuoi utenti. Una violazione di dati su qualche altro sito lascia una lista del genere, a volte con le password che la gente usava lì. Chi sa quali indirizzi sono tuoi utenti sa di chi vale la pena provare le vecchie password sulla tua app, e chi probabilmente aprirà un'email che sembra arrivare da te.
Supabase applica la stessa regola ovunque si inserisca un indirizzo. Il suo codice di registrazione, quando gli si chiede di creare un account per un'email già registrata e confermata, può restituire un utente inventato con un ID casuale, tramite una funzione il cui commento dice che esiste per evitare che trapeli se un utente è registrato o no. La reimpostazione della password risponde a un indirizzo sconosciuto con lo stesso successo vuoto che dà a uno conosciuto.
Sul lato della registrazione c'è un interruttore che controlli tu. Quell'utente
inventato è ciò che torna quando la conferma è richiesta sia per le
registrazioni via email sia per quelle via telefono. Se una delle due è
disattivata (quella del telefono conta anche se la tua app non usa mai l'accesso
via telefono), un indirizzo registrato riceve User already registered, codice
user_already_exists, e il tuo modulo di registrazione diventa da solo un
verificatore di liste.
Il messaggio utile, poi, deve venire da qualche parte. Supabase non dice alla tua
app se un'email ha un account, quindi per mostrare "Nessun account con questa
email" la tua app deve scoprirlo da sola. Vuol dire una ricerca che funzioni per
un visitatore non autenticato: una lettura della tua tabella profiles che
riesce solo se chiunque può leggere le email che contiene, oppure una funzione
del database che cerca in auth.users per chiunque lo chieda. In entrambi i
casi, l'elenco dei tuoi utenti diventa leggibile dall'esterno.
La prima delle due si vede dall'esterno, ed è così che la cerca la nostra scansione gratuita. Chiede a ogni tabella nominata dalla tua app quante righe potrebbe ottenere un visitatore senza login, legge solo il conteggio e richiede circa 20 secondi, senza account: scansiona la tua app.
Cosa mettere invece sulla tua pagina di accesso
Tieni il messaggio vago e metti ogni modo per rientrare sulla stessa pagina.
- Un solo messaggio per i tre casi, con parole semplici e le altre strade accanto: "Email e password non corrispondono. Se ti sei registrato con Google, usa il pulsante qui sotto." Aiuta l'utente Google e non dice a uno sconosciuto niente che la tua pagina di registrazione non dica già.
- Ogni metodo della tua pagina di registrazione. Il pulsante Google, il magic link e il campo password, tutti anche sulla pagina di accesso.
- Un link "Password dimenticata?" verso una pagina che ne imposti una. Supabase lì risponde allo stesso modo a ogni indirizzo, quindi può restare in vista, ed è così che una persona senza password ne ottiene una.
- Decisioni in base a
error.code. Mostra il tuo messaggio vago perinvalid_credentials, e "controlla la posta in arrivo" con un pulsante di reinvio peremail_not_confirmed. Il riferimento degli errori di Supabase chiede di riconoscere gli errori dal codice invece che dal testo del messaggio, e avverte che i codici di stato HTTP possono cambiare senza preavviso.
Nel codice sono due rami:
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error?.code === 'email_not_confirmed') showCheckYourInbox(email)
else if (error) showSignInHelp()
Se un builder scrive la tua pagina di accesso, puoi incollargli questi quattro punti come istruzione, compreso quello su ciò che la pagina deve tenere per sé.
Perché la tua pagina di accesso resti come l'hai lasciata
Dopo oggi possono ancora andare storte due cose: un prompt successivo riscrive la pagina per renderla "più utile", e una modifica altrove perde gli account a cui la pagina dà accesso.
Reeve Monitor continua a controllare da fuori:
- tutti e nove i controlli ogni ora, su un massimo di tre app, compreso quante righe un visitatore senza login può contare nelle tabelle nominate dalla tua app
- una email il giorno in cui una nuova scansione valuta la tua app peggio della precedente
- se l'app risponde, ogni 60 secondi
- un report mensile, in linguaggio semplice, di ciò che ha visto
Reeve Care tiene una copia del tuo database Supabase, account compresi:
- una copia cifrata secondo una pianificazione, sullo spazio di archiviazione di Reeve nell'UE, fuori dal tuo account Supabase
- lo schema auth in cui vivono gli account dei tuoi utenti, così tornano insieme a tutto il resto
- ogni copia verificata prima che conti, contando le righe di ogni tabella
- anche i tuoi file caricati, appena colleghi una credenziale di Storage
- un ripristino in un clic, con una copia di sicurezza dello stato attuale presa prima
- tutto quello che fa Monitor
Cosa fare adesso
Cosa fare
- Esegui la query su
auth.userscon l'indirizzo che ti ha dato la persona e guarda quale dei tre casi è. - Nessuna riga: chiedi con quale altro indirizzo potrebbe essersi registrata.
- Nessuna password: indicale il pulsante Google o GitHub che ha usato, o il tuo link di reimpostazione.
- Una password, e comunque rifiutata: mandale il link di reimpostazione.
- Mostra un messaggio vago per
invalid_credentials, e "controlla la posta in arrivo" solo peremail_not_confirmed. - Non aggiungere mai un controllo che dica a un visitatore se un'email ha un account.
Se il problema comincia un passo prima e i nuovi account non vengono nemmeno creati, Database error saving new user copre quel lato del modulo. La checklist di sicurezza in 10 minuti copre ciò che di solito resta aperto in un'app appena lanciata.
FAQ
Perché Supabase dice "Invalid login credentials" se la password è giusta?
Perché la password può essere giusta per un account che non esiste, oppure l'account può non avere alcuna password. Controlla che l'email sia quella usata per registrarsi, e se la persona si è registrata con Google o un altro pulsante di accesso, perché in quel caso l'account non ha password. Una query su auth.users nel SQL Editor mostra quale dei casi è.
"Invalid login credentials" vuol dire che l'account non esiste?
A volte. Supabase restituisce lo stesso messaggio, con lo stesso codice, invalid_credentials, quando nessun account ha quell'email, quando l'account non ha password e quando la password è sbagliata. Il messaggio da solo non ti dice quale, ed è fatto così.
Perché chi si è registrato con Google non può accedere con una password?
Perché il suo account è stato creato senza. Supabase controlla che ci sia una password salvata prima di confrontare quella digitata, e un account senza password riceve "Invalid login credentials". La persona può continuare a usare il pulsante Google, oppure usare il link di reimpostazione della tua app per impostarne una: Supabase invia quell'email a qualsiasi account trovi, comunque sia stato creato.
Il mio modulo di accesso dovrebbe dire che l'email non è registrata?
No. Quel messaggio dice a chiunque scriva un indirizzo se appartiene a uno dei tuoi utenti, e chi ha una lista di indirizzi trapelati può controllarli tutti. La tua app avrebbe anche bisogno di una ricerca che risponda a questa domanda per degli sconosciuti, ed è una seconda falla. Tieni il messaggio vago e mostra gli altri modi per accedere.
Che differenza c'è tra "Invalid login credentials" ed "Email not confirmed"?
Supabase restituisce "Email not confirmed" solo dopo aver controllato la password e averla trovata corretta. Quindi la persona giusta ha scritto la password giusta, e l'account aspetta che clicchi il link nella sua email di conferma. Puoi dirle senza rischi di controllare la posta in arrivo e offrirle di inviare di nuovo l'email.