Sicherheitsgrundlagen
Supabase "Invalid Refresh Token: Refresh Token Not Found": 3 Ursachen
Supabase "Invalid Refresh Token: Refresh Token Not Found" meldet deine Nutzer ab. Drei Ursachen, und wie das Auth-Log sie trennt.

Kurz gesagt
- Supabase "Invalid Refresh Token: Refresh Token Not Found" heißt: Supabase Auth kennt das Refresh Token nicht, das deine App geschickt hat. Die Sitzung lässt sich nicht verlängern, und der Nutzer wird abgemeldet.
- Drei Dinge löschen diesen Eintrag: eine Abmeldung (die in JavaScript standardmäßig jedes Gerät beendet), ein schon benutztes Token, das ein zweites Mal kommt, und ein Sitzungslimit in deinen Auth-Einstellungen.
- Manches davon ist eine harmlose Zeile aus einem alten Tab. Manches meldet jeden Tag alle Nutzer ab. Das Auth-Log zeigt dir, was davon, und jede Ursache hat ihre eigene Lösung.
Deine Nutzer schreiben dir, dass sie abgemeldet wurden. Einer war mitten in einem Formular, und eine andere ist sicher, dass sie nichts gedrückt hat. Wenn du die App selbst ausprobierst, läuft sie. Dann öffnest du deine Logs und findest immer wieder dieselbe Zeile:
AuthApiError: Invalid Refresh Token: Refresh Token Not Found
Supabase "Invalid Refresh Token: Refresh Token Not Found" ist Supabase Auth, der Teil von Supabase, der Leute anmeldet, und er sagt damit: Zu dem Token, das deine App geschickt hat, um eine Sitzung am Laufen zu halten, gibt es keinen Eintrag. Weil nichts mehr zu verlängern ist, meldet deine App den Nutzer ab.
Hier ist der Teil, den man leicht verkehrt herum versteht: Dieser Fehler ist der letzte Schritt, und die Ursache lag früher. Wenn Supabase das Token nicht mehr findet, ist die Sitzung, zu der es gehörte, schon vorbei. Beendet hat sie eine Abmeldung, ein Token, das zweimal kam, oder ein Limit, das jemand gesetzt hat. Ein länger gültiges Access Token oder das Abschalten der Prüfung, die eine Sitzung beendet, macht die Zeile in deinen Logs seltener und lässt die Ursache genau, wo sie war.
Was bedeutet "Invalid Refresh Token: Refresh Token Not Found"?
Es bedeutet, dass Supabase Auth ein Refresh Token bekommen hat, zu dem es keinen Eintrag hat, und die Sitzung deshalb nicht verlängert hat.
Jeder angemeldete Nutzer hat zwei Tokens. Das Access Token funktioniert wie ein Armband: Es weist ihn eine kurze Zeit lang aus, eine Stunde, solange du die Einstellung nicht geändert hast, und deine App zeigt es bei jeder Anfrage vor. Das Refresh Token ist ein Ticket. Bevor das Armband abläuft, bringt deine App das Ticket zum Schalter, und dieser Schalter ist Supabase Auth. Dort tauscht sie es gegen ein neues Armband und ein neues Ticket. Supabases Anleitung zu Sitzungen sagt, dass Refresh Tokens "never expire but can only be used once".
Refresh Token Not Found heißt, dass der Schalter ein Ticket nachschlägt und
nichts findet. Der Server antwortet mit HTTP 400 und dem Code
refresh_token_not_found, den Supabases
Liste der Auth-Fehlercodes
als "Session containing the refresh token not found." beschreibt. Ist auch das
Armband abgelaufen, entfernt
Supabases JavaScript-Client
die Sitzung aus dem Browser und teilt deiner App mit, dass sich der Nutzer
abgemeldet hat. In diesem Moment sieht dein Nutzer die Anmeldeseite.
Keins der beiden Tokens ist einer der API-Schlüssel deines Projekts, die sind
eine eigene Frage:
welche davon in deine App gehören.
Steht in deiner Meldung stattdessen Invalid JWT, wurde das Armband abgelehnt
und nicht das Ticket, und
dieser Fehler hat eine eigene Seite.
Kommt die Person danach nicht wieder hinein und das Anmeldeformular sagt
Invalid login credentials,
stecken hinter dieser Meldung drei verschiedene Fälle.
Warum werden meine Supabase-Nutzer abgemeldet?
Weil die Sitzung hinter ihrem Ticket geendet hat und das Armband, das sie noch tragen, sie angemeldet hielt, bis es ablief. Diese Verzögerung, mit der Standardeinstellung bis zu einer Stunde, lässt die Abmeldungen zufällig wirken: Der Nutzer fliegt eine Weile nach dem Ende der Sitzung raus.
Supabase Auth hat vier Arten zu sagen, dass ein Ticket nicht gilt, und jede zeigt auf eine andere Ursache. Das sind die genauen Meldungen aus dem Code des Supabase-Auth-Servers:
| Die Meldung in deinem Log | Der Code | Was passiert ist |
|---|---|---|
Invalid Refresh Token: Refresh Token Not Found | refresh_token_not_found | Der Schalter hat keinen Eintrag zu diesem Ticket. Eine Abmeldung hat seine Sitzung beendet, oder es wurde vor so langer Zeit getauscht, dass sein Eintrag weg ist. |
Invalid Refresh Token: Already Used | refresh_token_already_used | Das Ticket wurde vor mehr als zehn Sekunden getauscht und kommt wieder. Supabase beendet dann die ganze Sitzung. |
Invalid Refresh Token: Session Expired, manchmal mit (Inactivity) oder (Revoked by Newer Login) dahinter | session_expired | Ein Sitzungslimit in deinen Auth-Einstellungen hat sie beendet. |
Invalid Refresh Token: No Valid Session Found | session_not_found | Die Sitzung, zu der das Ticket gehörte, gibt es nicht mehr. |
Hinter diesen Meldungen stehen drei Ursachen, in Supabases eigener Dokumentation und in den Berichten auf seinen Issue-Trackern, und jede hat ihre eigene Lösung. Kann sich niemand mehr anmelden oder ist die ganze App leer, hast du ein anderes Problem, und ist Supabase down, oder ist es deine App ist der richtige Anfang.
Ein Refresh Token funktioniert einmal
Jedes Mal, wenn deine App eine Sitzung verlängert, gibt sie das Ticket ab, und der Schalter zerreißt das alte. Supabase nennt das Refresh Token Rotation. Das alte Ticket funktioniert noch zehn Sekunden weiter, was die Anleitung zu Sitzungen das Reuse Interval nennt, damit sich ein Server und ein Browser, die fast im selben Moment verlängern, nicht gegenseitig rauswerfen. Auch ein Client, der sein Ticket getauscht und die Antwort über eine schlechte Verbindung verloren hat, ist abgedeckt: Er bekommt das aktuelle Ticket zurück.
Außerhalb dieser beiden Fälle gilt ein altes Ticket, das zurückkommt, als
Warnzeichen, denn ein Token, das zweimal auftaucht, sieht aus wie ein gestohlenes.
Die Anleitung sagt, dass die ganze Sitzung dann "is regarded as terminated and
all refresh tokens belonging to it are marked as revoked". Die Antwort lautet
diesmal Already Used.
Ein weiterer Schritt macht aus Already Used ein Not Found. Der
Supabase-Auth-Server
löscht die Einträge zerrissener Tickets
24 Stunden, nachdem sie widerrufen wurden. Das veraltete Ticket, das heute
Already Used bekommt, bekommt also morgen Refresh Token Not Found, weil der
Schalter keinen Eintrag mehr hat, mit dem er es vergleichen könnte.
Ursache 1: Jemand hat sich abgemeldet, auf diesem Gerät oder einem anderen
Eine Abmeldung löscht die Sitzung auf der Seite des Schalters, und jedes Ticket
daraus wird eins, das der Schalter nicht findet. In Supabases JavaScript-Client
beendet signOut() ohne etwas in den Klammern jede Sitzung dieses Nutzers,
auf jedem Gerät. Supabases
Anleitung zum Abmelden sagt das
ausdrücklich: Mit dieser Voreinstellung scheitern die anderen Sitzungen mit
AuthApiError: Invalid Refresh Token: Refresh Token Not Found, sobald sie das
nächste Mal verlängern wollen, "even though no one signed out on that device."
Ein Nutzer, der sich auf dem Handy abmeldet, ist also auch auf dem Laptop abgemeldet, bis zu einer Stunde später, wenn das Armband des Laptops abläuft und sein Ticket als unbekannt zurückkommt. Von seinem Platz aus sieht es so aus, als hätte deine App ihn ohne Grund rausgeworfen.
Soll jedes Gerät für sich angemeldet bleiben, darf der Abmeldeaufruf nur die Sitzung beenden, aus der er kommt:
await supabase.auth.signOut({ scope: 'local' })
Bitte deinen Builder, diese Form überall zu verwenden, wo deine App Leute abmeldet. Der andere gewöhnliche Weg, eine Sitzung zu beenden, ist eine Passwortänderung, die Supabases Anleitung zu Sitzungen zu den Dingen zählt, die eine Sitzung beenden.
Ursache 2: Das neue Ticket ist nie im Browser angekommen
Rendert deine App Seiten auf einem Server, wie es eine Next.js-App tut, dann
verlängert auch dieser Server Sitzungen. Er liest das Ticket aus einem Cookie,
tauscht es am Schalter und muss das neue Ticket in einem Set-Cookie-Header
seiner Antwort an den Browser zurückschicken. Kommt dieses Cookie nicht an,
behält der Browser das alte Ticket, und die nächste Anfrage zeigt ein Ticket vor,
das der Server schon getauscht hat. Innerhalb von zehn Sekunden geht das durch.
Danach heißt es Already Used und die Sitzung endet, und einen Tag später ist
dasselbe Ticket Not Found.
Supabases
Anleitung zum Erstellen eines Server-Clients
nennt den Fehler, der das in einer Next.js-App auslöst. Der Code, der die Sitzung
verlängert und den Next.js 16 Proxy nennt und frühere Versionen Middleware, muss
die Antwort zurückgeben, in die die neuen Cookies geschrieben wurden: "An earlier
response doesn't carry the refreshed cookies, so the user is signed out on the
next request." Dieselbe Anleitung warnt, dass unter Next.js 15 und früher eine
Datei namens proxy.ts gar nicht ausgeführt wird, "so sessions never refresh and
users get signed out". Ein Assistent, der mit der neuesten Dokumentation arbeitet,
kann genau diese Datei in eine ältere App schreiben.
Supabases Anleitung sagt auch, warum sich dieser Fall schwer finden lässt: "it
looks like users being signed out at random rather than an error in your code."
Die Berichte klingen genauso. Ein Entwickler schrieb auf dem
Issue-Tracker von @supabase/ssr
"I'm inadvertently logged out of my web application every day", und der
Supabase-Entwickler, der antwortete, verwies darauf, wie die Middleware ihre
Antwort weitergab. Der
Bericht zu einer iOS-App
beschreibt die Abmeldung "2-3 days after logging in" und wurde geschlossen,
nachdem Updates an Supabases Swift-Bibliothek die Meldungen seltener gemacht
hatten.
Zum Prüfen öffnest du die Entwicklertools deines Browsers auf einer Seite, die
die Sitzung verlängert hat, und siehst dir die Antwort im Tab Network an. Sie
sollte ein Set-Cookie mit dem neuen Token tragen, und die nächste Anfrage
sollte genau dieses Token zurückschicken.
Ursache 3: ein Sitzungslimit in deinen Auth-Einstellungen
Ab dem Pro-Plan kann Supabase Sitzungen absichtlich beenden, über die Seite Sessions in den Auth-Einstellungen deines Projekts. Es gibt drei Limits, und die Anleitung zu Sitzungen beschreibt jedes:
- Zeitlich begrenzte Sitzungen enden nach einer festen Zeit, egal wie aktiv der Nutzer ist.
- Ein Inaktivitäts-Timeout beendet eine Sitzung, die eine Weile nicht verlängert wurde.
- Eine Sitzung pro Nutzer behält nur die neueste Anmeldung, also beendet eine Anmeldung auf dem Handy die Sitzung auf dem Laptop.
Diese sagen es in ihrer eigenen Meldung, Session Expired, beim zweiten und
dritten mit (Inactivity) oder (Revoked by Newer Login) dahinter. Die
Anleitung ergänzt, dass beendete Sitzungen 24 Stunden später gelöscht werden.
Ein Ticket aus einer solchen Sitzung, das danach zurückkommt, bekommt also
stattdessen Not Found.
Hast du eins davon eingestellt, sind die Abmeldungen die Einstellung bei der Arbeit. War es nicht so gemeint, oder hat eine Vorlage oder ein Assistent es für dich eingestellt, zeigt die Seite, welches Limit an ist.
Der Fall, der harmlos ist
Ein Ticket aus einer schon beendeten Sitzung, das aus einem alten Tab kommt oder
aus einem Browser, den wochenlang niemand geöffnet hat, bekommt auch
Refresh Token Not Found. Die Person dahinter war schon abgemeldet. Ihr Browser
hat einmal gefragt, Supabase hat nein gesagt, und sie sieht deine Anmeldeseite,
die sie ohnehin gesehen hätte.
Im Log sieht das so aus: eine Zeile für einen Nutzer, oft kurz nachdem sich dieser Nutzer irgendwo abgemeldet hat, und danach nichts mehr von ihm. Solche Zeilen sind der Fehler, der seine Arbeit macht. Verfolgen solltest du dieselben Leute immer wieder, oder viele verschiedene Leute am selben Tag.
So liest du deine Auth-Logs dafür
Öffne Logs in deinem Supabase-Dashboard, wähle den Log-Typ Auth und
durchsuche die Event-Meldungen nach refresh token. Wähl eine Zeile aus, um ihre
Details zu sehen, darunter die ID des Nutzers, und der Filter User grenzt die
Liste dann auf diese eine Person ein. Wie weit die Logs zurückreichen, hängt von
deinem Plan ab, also lies sie, solange die Beschwerden frisch sind.
Dann lies, was sich wiederholt:
| Was du im Auth-Log siehst | Worauf es hinweist |
|---|---|
Ein Refresh Token Not Found für einen Nutzer, und danach nichts | Ein alter Tab oder ein zurückkehrender Browser. Harmlos. |
Refresh Token Not Found für einen Nutzer kurz nach seiner Abmeldung woanders | Ursache 1, eine Abmeldung, die jedes Gerät beendet hat |
Already Used, einen Tag später Refresh Token Not Found, für dieselben Nutzer, immer wieder | Ursache 2, neue Tickets, die nicht im Browser ankommen |
Session Expired, mit einem Grund in Klammern | Ursache 3, ein Sitzungslimit |
Du kannst auch die Datenbank fragen, wie oft sich Leute anmelden. Jede Anmeldung
legt eine Zeile in auth.sessions an, und eine Abmeldung löscht sie. Füg das in
den SQL Editor ein:
select user_id, count(*) as sessions, max(created_at) as latest
from auth.sessions
where created_at > now() - interval '14 days'
group by user_id
order by sessions desc
limit 20;
Ein Nutzer mit viel mehr Sitzungen, als er Geräte hat, hat sich immer wieder angemeldet, ohne sich je abzumelden, und genau das tut jemand, den deine App ständig rauswirft.
Was du beim Beheben nicht ändern solltest
Deine Regeln für Row Level Security, die Reuse Detection und die Gültigkeit des Access Tokens. Alle drei sehen aus wie eine Lösung und lassen die Ursache, wo sie ist, und die erste zu lockern kann deine Daten für Fremde öffnen.
Lass deine Regeln für Row Level Security in Ruhe. Row Level Security sind die
Regeln, die entscheiden, welche Zeilen jeder Besucher bekommt. Solange Nutzer
rausfliegen, bleibt jeder Bildschirm, dessen Regel auth.uid() liest, für sie
leer, denn laut
Supabases Dokumentation
gibt auth.uid() null zurück, wenn die Sitzung abgelaufen ist. Die
naheliegende Lösung ist, diese Regel zu lockern, bis sich der Bildschirm wieder
füllt, und dann füllt er sich auch für Fremde. Hat das schon jemand getan, fragt
unser kostenloser Scan jede Tabelle, die deine App nennt, wie viele Zeilen ein
Besucher ohne Anmeldung bekäme, liest die Zahl, ohne eine einzige Zeile abzurufen,
und braucht ungefähr 20 Sekunden ohne Konto:
scanne deine App.
Lass die Reuse Detection eingeschaltet. Supabase lässt dich sie abschalten,
in den erweiterten Einstellungen auf der Seite Sessions, und nennt das "generally
not recommended". Ist sie aus, liefert Ursache 2 kein Already Used mehr, und
das neue Ticket kommt trotzdem nie im Browser an.
Lass das Access Token bei einer Stunde. Ein längeres Armband schiebt die Abmeldung auf und lässt ihre Ursache, wo sie ist. Supabases Anleitung sagt, dass ein Wert über einer Stunde "generally discouraged for security reasons" ist.
Damit Anmeldungen nach der Lösung halten
Zwei Dinge können noch schiefgehen, wenn die Abmeldungen aufhören: Eine spätere Änderung bringt sie zurück, und eine unterwegs gelockerte Regel lässt eine Tabelle für Fremde offen.
Reeve Monitor prüft weiter von außen:
- ob deine App erreichbar ist, jede Minute geprüft, damit eine Welle von "Ich wurde abgemeldet"-Nachrichten nicht für einen Ausfall gehalten wird
- die vollständige Sicherheitsprüfung jede Stunde, für bis zu drei Apps
- eine Nachricht, wenn sich ein Ergebnis ändert, damit eine beim Beheben gelockerte Regel nicht wartet, bis du hinschaust
- ein monatlicher Bericht in einfacher Sprache über das, was er gesehen hat
Reeve Care hält eine Kopie deiner Supabase-Datenbank:
- 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
- Durchsuch deine Auth-Logs nach
refresh tokenund lies, welche Meldung sich wiederholt, und bei wem. - Beendet eine Abmeldung auf einem Gerät die anderen, ändere sie auf
signOut({ scope: 'local' }). - Rendert deine App auf einem Server, sorg dafür, dass die Antwort, die eine Sitzung verlängert hat, auch die ist, die beim Browser ankommt, und prüf ihr
Set-Cookieim Tab Network. - Sieh auf der Seite Sessions deiner Auth-Einstellungen nach einer Zeitbegrenzung, einem Inaktivitäts-Timeout oder einer Sitzung pro Nutzer, die niemand einschalten wollte.
- Lass Row Level Security, die Reuse Detection und die Gültigkeit des Access Tokens, wie sie sind, solange du das behebst.
- Behandle ein
Not Foundpro Nutzer als Rauschen und dieselben Nutzer jeden Tag als Fehler.
Fang mit dem Auth-Log an, denn die Meldung, die sich wiederholt, sagt dir, welche der drei Ursachen du hast. Dann, sobald die Leute angemeldet bleiben, scanne deine App, um zu prüfen, dass unterwegs nichts gelockert wurde.
FAQ
Warum werden meine Supabase-Nutzer scheinbar zufällig abgemeldet?
Weil die Sitzung hinter ihrem Refresh Token geendet hat und das Access Token, das sie noch haben, sie bis zu einer Stunde lang angemeldet hält. Die Abmeldung kommt also später als ihre Ursache. Die üblichen Gründe sind eine Abmeldung auf einem anderen Gerät, ein erneuertes Token, das nie im Browser angekommen ist, und ein Sitzungslimit in deinen Auth-Einstellungen. Das Auth-Log zeigt, welcher es ist.
Was ist ein Refresh Token?
Es ist die langlebige Hälfte einer Supabase-Sitzung. Das Access Token weist den Nutzer standardmäßig eine Stunde lang aus, und bevor es abläuft, tauscht deine App das Refresh Token gegen ein neues Paar. Jedes Refresh Token funktioniert einmal, und das alte hört zehn Sekunden nach dem Tausch auf zu funktionieren.
Ist "Refresh Token Not Found" ein Zeichen für einen Angriff?
Für sich genommen nicht. Es heißt, dass Supabase Auth ein Token abgelehnt hat, von dem es keinen Eintrag hat, und das ist die richtige Antwort. Gegen ein gestohlenes Token schützt die Reuse Detection, die eine Sitzung beendet, wenn ein altes Token zurückkommt, und Supabase empfiehlt, sie eingeschaltet zu lassen.
Warum passiert das nur in Produktion?
Weil die meisten Ursachen Zeit oder ein zweites Gerät brauchen. Ein veraltetes Token wird am ersten Tag als schon benutzt abgelehnt und als nicht gefunden, sobald sein Eintrag einen Tag später gelöscht ist, und eine Sitzung, mit der du beim Bauen angemeldet bist, lebt selten so lange. Echte Nutzer melden sich außerdem auf mehreren Geräten an, und dort zeigt sich eine Abmeldung, die jede Sitzung beendet.
Wie lange hält eine Supabase-Sitzung?
Standardmäßig, bis sich der Nutzer abmeldet. Das Access Token hält eine Stunde, wenn du es nicht änderst, und das Refresh Token läuft nie ab, funktioniert aber nur einmal. Ab dem Pro-Plan kannst du Sitzungen nach Gesamtdauer, nach Inaktivität oder auf eine pro Nutzer begrenzen, auf der Seite Sessions deiner Auth-Einstellungen.