Zum Inhalt springen

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.

Vlad Tkachenko11 Min. Lesezeit
Ein Tastenfeld neben einer Tür, dessen einzige Lampe rot leuchtet, und davor ein Ausweis, für den das Tastenfeld keinen Schlitz hat.

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.

Ein falsches Passwort, eine unbekannte Adresse und ein Konto ohne Passwort landen bei derselben Prüfung und bekommen alle dieselbe Antwort.

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ückkommtWas es bedeutet
Keine ZeileIn 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 ÄhnlichesDie Person hat sich mit diesem Knopf registriert und nie ein Passwort gehabt
has_password ist trueDas eingegebene Passwort ist falsch, oder die Person hat sich per Magic Link registriert und kennt ihres nicht
email_confirmed_at ist leerDie 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.

Ein Formular, das ein fehlendes Konto nennt, sortiert jede Adressliste in deine Nutzer und alle anderen. Eine Antwort für alle lässt die Liste, wie sie war.

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 bei invalid_credentials und "Prüf dein Postfach" mit einem Knopf zum erneuten Senden bei email_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.users mit 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_credentials und "Prüf dein Postfach" nur bei email_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.

Geschrieben von

Vlad Tkachenko

Gründer, Reeve

Ich schaue mir den ganzen Tag Apps an, die mit Lovable, Bolt, v0, Cursor und Replit gebaut wurden, und die kurze Liste von Fehlern, die darin immer wieder auftauchen.

Mehr über den Autor

Weiterlesen

Alle Artikel

Sichere deine Supabase-Datenbank vor der nächsten Änderung

Reeve sichert sie verschlüsselt nach Zeitplan und stellt sie für dich wieder her, wenn etwas schiefgeht.

Supabase-Backups ansehen

Automatisierte externe Prüfung, kein vollständiges Audit. Das Fehlen von Funden ist keine Garantie für Sicherheit.