Sicherheitsgrundlagen
"Invalid login credentials" in Supabase: die drei Ursachen
Supabase sagt "Invalid login credentials" bei falschem Passwort, unbekannter E-Mail und Google-Konto. So erkennst du, welcher Fall es ist.

Kurz gesagt
- Supabase antwortet "Invalid login credentials" drei verschiedenen Menschen: jemandem mit falschem Passwort, jemandem, dessen E-Mail kein Konto hat, und jemandem, der sich mit Google registriert und nie ein Passwort hatte.
- Das ist Absicht. Ein Anmeldeformular, das sagt, welcher der drei Fälle es war, verrät jedem mit einer Liste von E-Mail-Adressen, welche davon deinen Nutzern gehören.
- Du kannst die drei selbst unterscheiden, mit einer Abfrage auf auth.users. Deine Anmeldeseite sollte die Meldung vage lassen und jeden Weg anbieten, auf dem das Konto entstanden sein kann.
Jemand schreibt dir, dass er sich nicht in deine App einloggen kann, und ist sicher, dass das Passwort stimmt. Du probierst seine Adresse selbst und bekommst dieselbe rote Zeile unter dem Formular:
Invalid login credentials
Supabase antwortet "Invalid login credentials" bei einem falschen Passwort. Mit genau denselben Worten antwortet es zwei anderen Menschen: jemandem, dessen E-Mail in deiner App kein Konto hat, und jemandem, der sich mit Google registriert hat und nie ein Passwort hatte. Deine App aus Lovable, Bolt, v0, Cursor, Claude Code oder Replit arbeitet so, wie sie gebaut ist. Sie gibt drei verschiedenen Situationen eine Antwort.
Hier liegt der Teil, den die naheliegende Lösung falsch macht: Die vage Meldung ist Absicht, und wer sie hilfreicher macht, reißt ein Loch. Was hilft, ist herauszufinden, welchen der drei Fälle du vor dir hast. Das sagt dir dein eigenes Dashboard, während das Formular weiter so wenig sagt wie jetzt.
Was bedeutet "Invalid login credentials" in Supabase?
Supabase Auth, der Teil von Supabase, der Leute anmeldet, hat eine E-Mail und ein Passwort abgelehnt und behält den Grund für sich.
Die Meldung steht im Code des Auth-Servers selbst. In
der Funktion, die eine Anmeldung mit Passwort verarbeitet,
kommt derselbe Fehler an drei Stellen zurück: wenn kein Nutzer diese E-Mail hat,
wenn für den Nutzer kein Passwort gespeichert ist und wenn das Passwort nicht
passt. Jedes Mal ist die Antwort HTTP 400 mit dem Code invalid_credentials,
den Supabases
Referenz der Fehlercodes
als "Login credentials or grant type not recognized." beschreibt.
In deiner App kommt er als AuthApiError aus signInWithPassword an, mit
error.code gleich invalid_credentials. Dieselbe Referenz sagt, Fehler immer
an error.code und error.name zu erkennen, und das zählt später, wenn du
entscheidest, was dein Formular sagt. Wenn deine Nutzer angemeldet waren und dann
hinausgeworfen wurden, ist das ein anderer Fehler mit
einer eigenen Seite.
Stell dir dein Anmeldeformular als Tastenfeld neben der Haustür eines Gebäudes vor. Jemand tippt einen Code ein, und eine Lampe antwortet. Grün öffnet die Tür. Rot leuchtet bei einem falschen Code, bei einem Code, den nie jemand bekommen hat, und bei einem Bewohner, der immer mit einem Ausweis durch die Seitentür gekommen ist und nie einen Code hatte.
Die drei Menschen hinter der Meldung
Jeder der drei braucht eine andere Lösung, deshalb lohnt es sich zu wissen, wen du vor dir hast.
Das falsche Passwort. Das Konto existiert, es hat ein Passwort, und die Eingabe passt nicht dazu. Dazu gehören ein Passwort, das auf einem anderen Gerät geändert und seitdem vergessen wurde, und ein Passwortmanager, der ein altes einträgt.
Die Adresse ohne Konto. Niemand in deinem Projekt hat sich mit dieser E-Mail registriert. Es kann eine zweite Adresse sein (die berufliche, wenn die Person sich mit der privaten registriert hat) oder ein Tippfehler bei der Registrierung, der jetzt als ihre E-Mail gespeichert ist.
Das Konto ohne Passwort. Die Person hat sich mit Google, GitHub oder einem anderen Anmeldeknopf registriert, und Supabase hat nie ein Passwort für sie gespeichert. Kommt sie zurück und tippt eins ein, gibt es nichts zum Vergleichen. Der Auth-Server prüft, ob ein Passwort gespeichert ist, bevor er sich das eingegebene ansieht, und ein Konto ohne bekommt dieselbe 400.
Wer sich mit einem Magic Link registriert hat, dem Anmeldelink per E-Mail, ist eine Spielart des ersten Falls. Supabases Code für Magic Links legt das Konto mit einem zufälligen Passwort an, das nie jemandem gezeigt wird, also ist jedes Passwort, das die Person eintippt, ein falsches.
Wie finde ich heraus, welcher Fall es war?
Frag deine eigene Datenbank. Jedes Konto in deinem Projekt ist eine Zeile in
einer Tabelle namens auth.users, und diese Zeile sagt, wie sich die Person
anmeldet.
Supabases Leitfaden zu Nutzern
beschreibt das Feld, auf das es ankommt: app_metadata.providers ist laut
Supabase die Liste der Anbieter, mit denen sich der Nutzer anmelden kann. Ein
Nutzer kann mehrere Identitäten haben, also kann eine Person sowohl ein Passwort
als auch Google haben. Ob überhaupt ein Passwort gespeichert ist, steht in der
Spalte, die der Server selbst prüft.
Öffne den SQL Editor in deinem Supabase-Dashboard und führ das mit der Adresse aus, die dir die Person genannt hat:
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');
Die Abfrage liest eine Zeile und zeigt nie ein Passwort, nur ob es eins gibt.
| Was zurückkommt | Was es bedeutet |
|---|---|
| Keine Zeile | In diesem Projekt hat kein Konto diese Adresse. Frag, welche andere Adresse die Person benutzt haben könnte |
has_password ist false, und signs_in_with nennt google, github oder Ähnliches | Die Person hat sich mit diesem Knopf registriert und nie ein Passwort gehabt |
has_password ist true | Das eingegebene Passwort ist falsch, oder die Person hat sich per Magic Link registriert und kennt ihres nicht |
email_confirmed_at ist leer | Die E-Mail ist noch nicht bestätigt, was eine andere Meldung ergibt, sobald das Passwort stimmt |
Wenn du lieber klickst als tippst, listet Authentication → Users im Dashboard dieselben Konten.
Das Konto, das nie ein Passwort hatte
Schick die Person zurück zu dem Knopf, den sie benutzt hat, oder lass sie über deinen Link zum Zurücksetzen ein Passwort setzen.
Am Tastenfeld ist das der Bewohner, der immer die Seitentür genommen hat. Für ihn ist kein Code hinterlegt, also wird jeder Code abgelehnt, den er probiert.
Die erste Reparatur liegt auf deiner Anmeldeseite. Wenn die Registrierung "Mit Google fortfahren" anbietet, braucht die Anmeldeseite denselben Knopf, damit jemand, der sich damit registriert hat, ihn sieht, wenn er zurückkommt.
Möchte die Person zusätzlich ein Passwort, erledigt das dein Link "Passwort
vergessen?" schon. Supabases
Code zum Zurücksetzen des Passworts
schickt die Wiederherstellungs-E-Mail an jedes Konto, das er findet, ohne zu
prüfen, wie das Konto entstanden ist. Ein Google-Nutzer, der dem Link folgt und
ein neues Passwort setzt, kann sich danach auf beide Arten anmelden. Das setzt
voraus, dass deine App die Seite hat, die das neue Passwort setzt, also
supabase.auth.updateUser({ password }) aufruft. Landet der Link auf einer
Seite, die nie nach einem fragt, ist die Person einmal angemeldet und hat
weiterhin kein Passwort.
"Email not confirmed" heißt, das Passwort war richtig
Es ist eine andere Meldung mit einem anderen Code, und sie erreicht nur jemanden, der die Passwortprüfung schon bestanden hat.
In derselben Funktion des Auth-Servers kommt die Prüfung auf eine bestätigte
E-Mail nach der Passwortprüfung. Email not confirmed, Code
email_not_confirmed, geht also nur an jemanden, der eine existierende E-Mail
und ihr richtiges Passwort eingegeben hat. Supabases Fehlerreferenz formuliert es
als "Signing in is not allowed for this user as the email address is not
confirmed." Am Tastenfeld ist das eine andere Lampe: Der Code war richtig, und
die Tür bleibt zu, bis der Papierkram für den Einzug erledigt ist.
Du siehst sie, wenn Confirm email beim Email-Anbieter deines Projekts
eingeschaltet ist. Laut Supabases
Konfigurationsleitfaden
müssen Nutzer dann ihre E-Mail-Adresse bestätigen, bevor sie sich zum ersten Mal
anmelden. Die Lösung ist die E-Mail, die noch nicht angeklickt wurde: Sag der
Person, dass sie Posteingang und Spam-Ordner prüfen soll, und biete an, sie mit
supabase.auth.resend({ type: 'signup', email }) erneut zu schicken. Kommen
diese E-Mails gar nicht an, ist
das E-Mail-Limit das Erste, was du
ausschließen solltest. Kommen sie an und der Link sagt dann "Email link is
invalid or has expired",
hat ihn wahrscheinlich etwas vor der Person geöffnet.
Weil diese Meldung erst nach einem richtigen Passwort erscheint, gibt der Hinweis auf das Postfach einem Fremden nichts, was er nicht schon hatte.
Warum die Meldung vage bleibt
Weil die hilfreiche Version Fremden verraten würde, wer deine Nutzer sind.
Stell dir vor, das Tastenfeld würde antworten "In Wohnung 4B wohnt niemand". Wer vorbeigeht, könnte die Liste der Wohnungen durchgehen und erfahren, welche bewohnt sind.
Dein Formular funktioniert genauso. Würde es "Kein Konto mit dieser E-Mail" sagen, könnte jeder mit einer Liste von Adressen jede einzeln eintippen und erfahren, welche deinen Nutzern gehören. Ein Datenleck bei irgendeiner anderen Website hinterlässt so eine Liste, manchmal mit den Passwörtern, die die Leute dort benutzt haben. Wer weiß, welche Adressen deine Nutzer sind, weiß, wessen alte Passwörter es sich lohnt, in deiner App zu probieren, und wer eine E-Mail öffnen dürfte, die aussieht, als käme sie von dir.
Supabase wendet dieselbe Regel überall an, wo eine Adresse eingegeben wird. Sein Registrierungscode kann, wenn er ein Konto für eine schon registrierte und bestätigte E-Mail anlegen soll, einen erfundenen Nutzer mit zufälliger ID zurückgeben, über eine Funktion, deren Kommentar sagt, sie solle verhindern, dass nach außen dringt, ob ein Nutzer registriert ist. Das Zurücksetzen des Passworts beantwortet eine unbekannte Adresse mit demselben leeren Erfolg wie eine bekannte.
Auf der Seite der Registrierung gibt es einen Schalter, den du kontrollierst.
Der erfundene Nutzer kommt zurück, wenn die Bestätigung sowohl für E-Mail- als
auch für Telefon-Registrierungen verlangt wird. Ist eine der beiden
ausgeschaltet (die für Telefon zählt auch dann, wenn deine App nie eine
Anmeldung per Telefon nutzt), bekommt eine registrierte Adresse
User already registered, Code user_already_exists, und dein
Registrierungsformular wird selbst zum Listenprüfer.
Die hilfreiche Meldung muss außerdem irgendwoher kommen. Supabase sagt deiner App
nicht, ob eine E-Mail ein Konto hat, also muss deine App es selbst herausfinden,
um "Kein Konto mit dieser E-Mail" anzuzeigen. Das heißt eine Abfrage, die für
einen nicht angemeldeten Besucher funktioniert: ein Lesen deiner Tabelle
profiles, das nur gelingt, wenn jeder die E-Mails darin lesen kann, oder eine
Datenbankfunktion, die auth.users für jeden durchsucht, der fragt. So oder so
wird die Liste deiner Nutzer von außen lesbar.
Das Erste davon lässt sich von außen sehen, und genau so sucht unser kostenloser Scan danach. Er fragt jede Tabelle, die deine App nennt, wie viele Zeilen ein Besucher ohne Login bekommen könnte, liest nur die Anzahl und braucht etwa 20 Sekunden, ohne Konto: scanne deine App.
Was stattdessen auf deine Anmeldeseite gehört
Lass die Meldung vage und biete jeden Weg zurück auf derselben Seite an.
- Eine Meldung für alle drei Fälle, in einfachen Worten, mit den anderen Wegen daneben: "E-Mail und Passwort passen nicht zusammen. Wenn du dich mit Google registriert hast, nutze den Knopf unten." Sie hilft dem Google-Nutzer und verrät einem Fremden nichts, was deine Registrierungsseite nicht auch zeigt.
- Jede Methode deiner Registrierungsseite. Der Google-Knopf, der Magic Link und das Passwortfeld, alle auch auf der Anmeldeseite.
- Ein Link "Passwort vergessen?" zu einer Seite, die eins setzt. Supabase beantwortet dort jede Adresse gleich, also kann er sichtbar sein, und so kommt eine Person ohne Passwort zu einem.
- Entscheidungen nach
error.code. Zeig deine vage Meldung beiinvalid_credentialsund "Prüf dein Postfach" mit einem Knopf zum erneuten Senden beiemail_not_confirmed. Supabases Fehlerreferenz sagt, Fehler am Code statt am Meldungstext zu erkennen, und warnt, dass sich HTTP-Statuscodes unerwartet ändern können.
Im Code sind das zwei Zweige:
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error?.code === 'email_not_confirmed') showCheckYourInbox(email)
else if (error) showSignInHelp()
Wenn ein Builder deine Anmeldeseite schreibt, kannst du diese vier Punkte als Anweisung einfügen, auch den darüber, was die Seite für sich behalten muss.
Damit deine Anmeldeseite so bleibt, wie du sie verlassen hast
Zwei Dinge können nach heute noch schiefgehen: Ein späterer Prompt schreibt die Seite "hilfreicher" um, und eine Änderung an anderer Stelle verliert die Konten, in die die Seite Leute einloggt.
Reeve Monitor prüft weiter von außen:
- alle neun Prüfungen jede Stunde, für bis zu drei Apps, darunter, wie viele Zeilen ein Besucher ohne Login in den Tabellen zählen kann, die deine App nennt
- eine E-Mail an dem Tag, an dem ein erneuter Scan deine App schlechter benotet als der vorige
- ob die App erreichbar ist, alle 60 Sekunden
- ein monatlicher Bericht in einfacher Sprache über das, was er gesehen hat
Reeve Care hält eine Kopie deiner Supabase-Datenbank, samt Konten:
- eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
- das Auth-Schema, in dem die Konten deiner Nutzer liegen, damit sie mit allem anderen zurückkommen
- jede Kopie geprüft, bevor sie zählt, durch Zählen der Zeilen in jeder Tabelle
- auch deine hochgeladenen Dateien, sobald du einen Storage-Zugang verbindest
- eine Wiederherstellung per Klick, mit einer Sicherheitskopie des aktuellen Stands vorab
- alles, was Monitor tut
Was du jetzt tun solltest
Was zu tun ist
- Führ die Abfrage auf
auth.usersmit der Adresse aus, die dir die Person genannt hat, und lies ab, welcher der drei Fälle es ist. - Keine Zeile: Frag, mit welcher anderen Adresse sich die Person registriert haben könnte.
- Kein Passwort: Verweis sie auf den Google- oder GitHub-Knopf, den sie benutzt hat, oder auf deinen Link zum Zurücksetzen.
- Ein Passwort, und trotzdem abgelehnt: Schick ihr den Link zum Zurücksetzen.
- Zeig eine vage Meldung bei
invalid_credentialsund "Prüf dein Postfach" nur beiemail_not_confirmed. - Bau nie eine Prüfung ein, die einem Besucher sagt, ob eine E-Mail ein Konto hat.
Wenn das Problem einen Schritt früher beginnt und neue Konten gar nicht erst angelegt werden, deckt Database error saving new user dieses Ende des Formulars ab. Die 10-Minuten-Sicherheits-Checkliste deckt ab, was in einer frisch gestarteten App sonst oft offen bleibt.
FAQ
Warum sagt Supabase "Invalid login credentials", obwohl das Passwort stimmt?
Weil das Passwort vielleicht zu einem Konto gehört, das es nicht gibt, oder das Konto gar kein Passwort hat. Prüf, ob die E-Mail die ist, mit der sich die Person registriert hat, und ob sie sich mit Google oder einem anderen Anmeldeknopf registriert hat, denn dann hat das Konto kein Passwort. Eine Abfrage auf auth.users im SQL Editor zeigt, welcher Fall es ist.
Heißt "Invalid login credentials", dass das Konto nicht existiert?
Manchmal. Supabase gibt dieselbe Meldung mit demselben Code, invalid_credentials, zurück, wenn kein Konto diese E-Mail hat, wenn das Konto kein Passwort hat und wenn das Passwort falsch ist. Die Meldung allein sagt dir nicht, welcher Fall es ist, und so ist sie gebaut.
Warum kann sich jemand, der sich mit Google registriert hat, nicht mit Passwort anmelden?
Weil sein Konto ohne Passwort angelegt wurde. Supabase prüft, ob überhaupt ein Passwort gespeichert ist, bevor es das eingegebene vergleicht, und ein Konto ohne bekommt "Invalid login credentials". Die Person kann weiter den Google-Knopf nutzen oder über den Link zum Zurücksetzen deiner App ein Passwort setzen: Supabase schickt diese E-Mail an jedes Konto, das es findet, egal wie es entstanden ist.
Soll mein Anmeldeformular sagen, dass die E-Mail nicht registriert ist?
Nein. Diese Meldung verrät jedem, der eine Adresse eintippt, ob sie einem deiner Nutzer gehört, und wer eine Liste geleakter Adressen hat, kann alle prüfen. Deine App bräuchte außerdem eine Abfrage, die Fremden diese Frage beantwortet, und das ist ein zweites Loch. Lass die Meldung vage und zeig die anderen Wege zur Anmeldung.
Was ist der Unterschied zwischen "Invalid login credentials" und "Email not confirmed"?
Supabase gibt "Email not confirmed" erst zurück, nachdem das Passwort geprüft und für richtig befunden wurde. Die richtige Person hat also das richtige Passwort eingegeben, und das Konto wartet darauf, dass sie den Link in ihrer Bestätigungs-E-Mail anklickt. Es ist sicher, ihr zu sagen, dass sie ihr Postfach prüfen soll, und anzubieten, die E-Mail erneut zu schicken.