Zum Inhalt springen

Sicherheitsgrundlagen

Supabase "Auth session missing!": wo die Sitzung geblieben ist

"Auth session missing!" heißt: supabase-js fand keine angemeldete Sitzung und fragte Supabase gar nicht erst. Wo die Sitzung blieb, und die Lösung je Fall.

Vlad Tkachenko11 Min. Lesezeit
Eine leere Ausweishülle an einem Band, der Platz für den Ausweis gelb umrissen, vor zwei Ausweisen mit Gesicht.

Kurz gesagt

  • "Auth session missing!" (AuthSessionMissingError, Status 400) kommt aus supabase-js in deiner App. Die Bibliothek suchte eine angemeldete Sitzung, fand keine und schickte die Anfrage nie an Supabase.
  • Auf einer Seite, auf der sich niemand angemeldet hat, ist das die richtige Antwort, und dein Code muss sie nur behandeln. Auf einer Seite zum Zurücksetzen des Passworts heißt es, dass der Link hier niemanden angemeldet hat. In einer Edge Function oder auf einem Server wurde die Sitzung des Besuchers nie weitergereicht.
  • Lass getUser() stehen. Wer auf einem Server stattdessen getSession() einsetzt, lässt den Fehler verschwinden, indem er glaubt, was immer im Cookie steht.

Deine Lovable- oder Bolt-App meldet Leute mit Supabase an. Eines Morgens öffnest du die Browserkonsole und findest das hier in Rot, auf einer Seite, an der nichts falsch aussieht:

AuthSessionMissingError: Auth session missing!

Oder eine Kundin schreibt dir, dass die Seite "Neues Passwort festlegen" sie mit genau diesen Worten abweist, jedes Mal.

Such nach "auth session missing supabase", und du findest Threads voller Lösungen: getUser() gegen getSession() tauschen, die Bibliothek herunterstufen, eine Wartezeit einbauen, das Projekt neu starten. Sie gehen davon aus, dass zwischen deiner App und Supabase etwas kaputtgegangen ist. Es wurde keine Anfrage gestellt. Der Fehler entsteht in deiner App, geschrieben von der Supabase-Bibliothek, bevor irgendetwas den Browser verlässt.

Was bedeutet "Auth session missing!" in Supabase?

supabase-js wurde gefragt, wer angemeldet ist, suchte eine Sitzung, fand keine und antwortete selbst.

Eine Sitzung ist das, was Supabase Auth einer Person bei der Anmeldung gibt: ein Paar Tokens, die sagen, wer sie ist, im Browser aufbewahrt, damit auch der nächste Seitenaufruf sie noch kennt. supabase-js ist die Bibliothek, mit der deine App mit Supabase spricht, und jeder Aufruf, der eine Person braucht, beginnt damit, diese Sitzung hervorzuholen.

Die Meldung steht in supabase-js selbst, in der Datei errors.ts der Auth-Bibliothek: ein AuthSessionMissingError mit dem Text Auth session missing! und dem Status 400. Die 400 ist ein Etikett, das sich die Bibliothek selbst gibt. Kein Server hat damit geantwortet, und die Logs von Supabase zeigen nichts, weil getUser() vor allem anderen nach einer Sitzung sieht. Ein Kommentar in seinem Quellcode sagt es so: Es gibt einen Fehler zurück, wenn es weder ein Zugriffstoken noch einen eigenen Authorization-Header gibt.

Stell dir einen Empfang vor, der dir sagen kann, welcher Besucher jemand ist. Wer sich anmeldet, bekommt an der Tür einen Ausweis, und supabase-js bewahrt diesen Ausweis in der Tasche des Browsers auf. Wenn dein Code fragt "Wer ist das?", greift supabase-js zuerst in die Tasche. Ist ein Ausweis darin, trägt es ihn zum Empfang und bringt einen Namen zurück. Ist die Tasche leer, geht es gar nicht erst zum Empfang. Es kommt sofort zurück und sagt dir, dass kein Ausweis da ist.

Mit einer Sitzung fragt supabase-js bei Supabase Auth nach. Ohne eine bleibt es in deiner App stehen, und der Fehler kommt von dort.

Er wird zurückgegeben, nicht geworfen

getUser() gibt den Fehler neben einem leeren Nutzer zurück, und dein Code entscheidet, was als Nächstes passiert.

Der Aufruf sieht so aus, und ohne Sitzung ist user danach null und error der AuthSessionMissingError:

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

Es wird nichts geworfen, also fängt ein try … catch um den Aufruf nichts. Von dort kommt der sichtbare Schaden, in einer von zwei Formen:

  • Der Code zeigt jeden error an, den er bekommt. Ein Besucher, der sich noch nicht angemeldet hat, sieht "Auth session missing!" in einer Meldung oder einem roten Kasten, auf einer Seite, die er sehen durfte.
  • Der Code benutzt den Nutzer, ohne nachzusehen. Er liest user.id, und die Seite bricht mit Cannot read properties of null ab, was nach einem ganz anderen Fehler aussieht.

Beides ist derselbe Fehler: "Niemand ist angemeldet" wird als Störung behandelt. Sieh zuerst nach dem Nutzer und zeig einem abgemeldeten Besucher die Seite, die für ihn gedacht ist:

const { data: { user } } = await supabase.auth.getUser()
if (!user) {
  // niemand ist angemeldet: Anmeldeseite oder öffentliche Seite zeigen
  return
}

Wenn du das von einer echten Störung unterscheiden willst, nennt die Bibliothek den Fall beim Namen: error.name ist 'AuthSessionMissingError'.

Wo die Sitzung verloren geht

An drei Stellen, und nur die erste ist harmlos.

1. In diesem Browser hat sich niemand angemeldet. Deine App ruft getUser() auf einer Seite auf, die jeder öffnen kann, etwa um zu entscheiden, ob "Anmelden" oder "Mein Konto" erscheint. Für einen Besucher ist die leere Tasche die zutreffende Antwort. Hier begegnen die meisten der Meldung, und die Code-Lösung oben ist alles, was es braucht.

2. Eine Seite für das Zurücksetzen des Passworts oder einen Magic Link. Ein neues Passwort setzt updateUser({ password }), und der Passwort-Leitfaden von Supabase ist bei der Reihenfolge eindeutig: Erst wenn eine Sitzung besteht, sammelt man das neue Passwort ein und ruft updateUser auf. Auf dieser Seite soll die Sitzung aus dem Link in der E-Mail kommen, der die Person beim Öffnen anmeldet. Tut er das nicht, ist die Tasche leer, und updateUser() antwortet mit "Auth session missing!". Die üblichen Gründe:

  • Der Link war schon benutzt oder abgelaufen, bevor er geöffnet wurde. Warum Supabase sagt, der E-Mail-Link sei ungültig oder abgelaufen geht jeden Grund durch.
  • Er wurde in einem anderen Browser oder einer anderen App geöffnet als dem, der ihn angefordert hat. Nutzt eine App die PKCE-Anmeldung von Supabase, bleibt ein Teil des Austauschs in dem Browser, der ihn begonnen hat, und laut der Dokumentation von Supabase muss der Austausch des Codes im selben Browser und auf demselben Gerät angestoßen werden, auf dem der Ablauf begann.
  • Er schickte die Person woandershin, etwa nach localhost, sodass die Sitzung an einer Adresse landete, die nicht deine App ist. Warum Supabase auf localhost umleitet behandelt diesen Fall.

3. Dein Server oder eine Edge Function hat die Sitzung nie bekommen. Der Ausweis liegt im Browser deines Besuchers. Code, der woanders läuft, eine Supabase Edge Function oder die Serverhälfte einer Next.js-App aus v0 oder Cursor, erzeugt seinen eigenen Supabase-Client, und ein neuer Client beginnt mit leerer Tasche. Solange die Sitzung des Besuchers nicht hineingereicht wird, findet getUser() dort nichts und antwortet mit "Auth session missing!", ohne Supabase zu fragen.

In einer Edge Function reist die Sitzung im Header Authorization der Anfrage mit, und supabase-js nutzt sie nur, wenn du sie beim Erzeugen des Clients übergibst. In einer Next.js-App reist sie in Cookies, und der Server-Helfer von Supabase liest sie nur, wenn du ihm die Cookies der Anfrage gibst. Reicht eine Edge Function diesen Fehler als Fehler zurück, zeigt deine App "Edge Function returned a non-2xx status code", und im Log der Funktion wartet diese Meldung.

Die Sitzung gehört dem Browser. Eine Funktion oder ein Server hat sie nur, wenn die Anfrage sie mitbringt und der Code sie an seinen Client weitergibt.

Woran du erkennst, welcher Fall es ist

Sieh, wo die Meldung erscheint, und dann, was der Browser tatsächlich geschickt hat.

In der Browserkonsole, auf einer Seite, die jeder öffnen kann. Fall 1. Öffne die Entwicklerwerkzeuge deines Browsers (F12), wähle Application, dann Local Storage und die Adresse deiner App. supabase-js bewahrt die Sitzung unter einem Schlüssel auf, der aus sb-, der ID deines Projekts und -auth-token besteht. Gibt es keinen solchen Eintrag, hat dieser Browser keine Sitzung, und die Meldung sagt die Wahrheit. Eine Next.js-App mit den Server-Helfern von Supabase bewahrt die Sitzung stattdessen in Cookies auf, also sieh unter Cookies nach demselben Namen.

Auf einer Seite zum Zurücksetzen oder für einen Magic Link. Fall 2. Sieh dir die Adresse an, unter der sich der Link geöffnet hat. Ist es nicht deine App, oder ist es ein Link, den du oder ein Mail-Scanner schon geöffnet hatte, hatte die Sitzung nie eine Chance anzukommen.

Im Log einer Edge Function oder deines Servers. Fall 3. Öffne den Tab Network, wiederhole die Aktion und klick auf die Anfrage an deine Funktion. Enthält ihr Header Authorization eine lange Zeichenkette, die mit eyJ beginnt, hat der Browser das Token einer Person geschickt, und deine Funktion hat es nicht an ihren Client weitergegeben.

Was du auf keinem Weg findest, ist eine Anfrage an /auth/v1/user, die mit dieser Meldung zurückkam. Siehst du doch eine, die fehlgeschlagen ist, ist es ein anderer Fehler. Eine Antwort mit invalid claim: missing sub claim heißt, dass der Schlüssel deines Projekts dorthin geschickt wurde, wo eine Person hingehört, und dieser Beitrag ist sein Zwilling.

Die Lösung für jeden Fall

Gib jedem Fall die Sitzung, die ihm fehlt, oder eine Antwort, die keine braucht.

Fall 1, der abgemeldete Besucher: Sieh nach user, bevor du ihn benutzt, wie im Code oben, und zeig die Seite, die für jemanden gedacht ist, der sich nicht angemeldet hat.

Fall 2, die Seite zum Zurücksetzen: Warte, bis der Link die Person angemeldet hat, bevor du das Formular zeigst. Supabase sendet das Ereignis PASSWORD_RECOVERY, wenn jemand über einen Wiederherstellungslink ankommt, und seine Referenz zeigt die Seite, wie sie darauf wartet:

supabase.auth.onAuthStateChange((event) => {
  if (event === 'PASSWORD_RECOVERY') {
    // der Link hat die Person angemeldet: jetzt das Formular für das neue Passwort zeigen
  }
})

Kommt das Ereignis nie, hat der Link nicht funktioniert. Sag das und biete an, einen neuen zu schicken, statt ein Formular zu zeigen, das sicher scheitern wird.

Fall 3, die Funktion oder der Server: Gib die Sitzung des Besuchers an den Client weiter, den du erzeugst. In einer Edge Function erzeugst du ihn mit dem eingehenden Header:

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' },
  })
}

Der neuere Wrapper withSupabase({ auth: 'user' }) von Supabase erledigt dasselbe, bevor dein Handler läuft, und der Beitrag zur Edge Function zeigt die Einrichtung. Behalte die 401 für einen Aufrufer ohne Sitzung, damit eine Anfrage von einer abgemeldeten Seite eine klare Ablehnung bekommt. Mit weitergereichtem Header kann getUser() für einen abgemeldeten Aufrufer dort stattdessen invalid claim: missing sub claim zurückgeben: derselbe Besucher, von der anderen Seite gesehen, und die 401 deckt beides ab.

In einer Next.js-App erzeugst du den Server-Client mit dem Helfer @supabase/ssr von Supabase und den Cookies der Anfrage, wie es seine Server-Einrichtung zeigt, und rufst getUser() auf diesem Client auf.

Zwei Lösungen, nach denen du schlechter dastehst

Beide werden für diese Meldung vorgeschlagen, und jede stoppt den Fehler, indem sie die Prüfung dahinter entfernt.

getUser() gegen getSession() tauschen. Im Browser ändert das nichts, was zählt: Der Speicher ist immer noch leer, also bekommst du keine Sitzung und keinen Nutzer, nur ohne die Meldung. Auf einem Server ist es gefährlich. Der Leitfaden von Supabase für die Serverseite sagt, man solle supabase.auth.getSession() in Servercode nie trauen, weil es die Sitzung aus dem Cookie liest, ohne sie neu zu prüfen. Ein Cookie ist etwas, das dir der Besucher schickt, und jeder kann dir jedes Cookie schicken, das er will. getUser() ist die Methode, von der supabase-js dokumentiert, dass sie eine Anfrage an den Server von Supabase Auth stellt, sodass ihr Ergebnis echt ist. Behalte sie überall, wo die Antwort entscheidet, was jemand darf.

Den geheimen Schlüssel in den Client setzen, damit der Aufruf nicht mehr scheitert. Ein Client mit dem Schlüssel service_role oder sb_secret_ braucht keine Sitzung zum Lesen und Schreiben, weil er die Regeln deiner Datenbank ganz übergeht. Die Funktion läuft wieder, für jeden Aufrufer, angemeldet oder nicht. Macht ein Builder diese Änderung in Code, der an den Browser geht, reist der Schlüssel mit zu jedem Besucher. Ist das schon passiert, rotiere ihn zuerst.

Das lässt sich von außen prüfen. Unser kostenloser Scan liest das JavaScript deiner Live-App so, wie ein Fremder es täte, und nennt jeden Schlüssel, den er findet, samt der Frage, ob er dorthin gehört. Das dauert etwa 20 Sekunden und braucht kein Konto: scanne deine App.

Damit die Prüfung nach der nächsten Änderung bleibt

Die Prüfung, wer angemeldet ist, ist der Teil einer Seite, der am ehesten verschwindet, wenn du deinen Builder bittest, einen Fehler wegzumachen, und beide schnellen Lösungen oben entfernen sie.

Reeve Monitor scannt deine Live-App jede Stunde neu:

  • alle neun Checks jede Stunde, für bis zu drei Apps
  • eine E-Mail an dem Tag, an dem ein erneuter Scan deine App schlechter benotet als der vorige; ein geheimer Schlüssel in deinem JavaScript deckelt die Note allein schon bei D
  • ob die App erreichbar ist, alle 60 Sekunden
  • ein monatlicher Bericht darüber, was gesehen wurde

Ein Server, der jedem Cookie glaubt, oder eine Funktion, die mit dem geheimen Schlüssel läuft, lässt einen Fremden die Zeilen deiner Nutzer ändern. Reeve Care hält eine Kopie deiner Supabase-Datenbank von davor.

  • eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
  • jede Kopie wird geprüft, bevor sie zählt, durch Zählen der Zeilen in jeder Tabelle
  • eine Wiederherstellung per Klick, die sichert, was jetzt da ist, bevor sie es ersetzt
  • deine hochgeladenen Dateien ebenfalls, sobald du eine Storage-Zugangsberechtigung verbindest
  • alles, was Monitor tut

Was jetzt zu tun ist

Was zu tun ist

  • Lies "Auth session missing!" als "hier ist niemand angemeldet". Die Anfrage hat deine App nie verlassen.
  • Sieh nach, ob user gleich null ist, bevor du ihn benutzt, und zeig einem abgemeldeten Besucher die Seite, die für ihn gedacht ist, statt des Fehlers.
  • Warte auf einer Seite zum Zurücksetzen auf das Ereignis PASSWORD_RECOVERY, bevor du das Formular zeigst, und biete einen neuen Link an, wenn es nie ankommt.
  • Gib in einer Edge Function oder auf einem Server den Header Authorization oder die Cookies des Besuchers an den Client weiter, den du erzeugst, und antworte mit 401, wenn es trotzdem keinen Nutzer gibt.
  • Behalte getUser() auf dem Server. getSession() glaubt dort jedem Cookie, das es geschickt bekommt.

Gibt es deine Sitzungen und verschwinden sie dann, sodass Leute, die angemeldet waren, plötzlich abgemeldet sind, ist das ein anderes Problem mit eigenen Ursachen, und warum Supabase-Nutzer abgemeldet werden behandelt es. Die 10-Minuten-Sicherheitscheckliste behandelt, was eine frisch gestartete App sonst noch oft offen lässt.

FAQ

Was bedeutet "Auth session missing!" in Supabase?

Es bedeutet, dass supabase-js, die Supabase-Bibliothek in deiner App, nach dem angemeldeten Nutzer gefragt wurde und keine Sitzung hatte, mit der sie fragen konnte. Eine Sitzung ist das, was Supabase Auth einer Person bei der Anmeldung gibt, und die Bibliothek bewahrt sie im Browser auf. Ist keine da, wendet sie sich gar nicht an Supabase. Sie gibt AuthSessionMissingError mit dem Status 400 zurück und als Nutzer null.

Warum sehe ich "Auth session missing!", wenn niemand angemeldet ist?

Weil das die Antwort für einen Besucher ist, der sich nicht angemeldet hat. Deine App hat getUser() auf einer Seite aufgerufen, die jeder öffnen kann, und supabase-js hat gemeldet, dass niemand angemeldet war. Nichts ist kaputt. Dein Code muss den Nutzer nur als null lesen und die Anmeldeseite zeigen, statt den Fehler anzuzeigen.

Warum zeigt meine Seite zum Zurücksetzen des Passworts "Auth session missing!"?

Ein Passwort mit updateUser() zu ändern braucht eine Sitzung, und auf dieser Seite soll die Sitzung aus dem Link in der E-Mail kommen. War der Link schon benutzt oder abgelaufen, wurde er in einem anderen Browser geöffnet als dem, der ihn angefordert hat, oder schickte er die Person an eine andere Adresse, dann kam keine Sitzung an, und updateUser() hat niemanden, den es ändern könnte. Warte auf das Ereignis PASSWORD_RECOVERY, bevor du das Formular zeigst, und biete einen neuen Link an, wenn es nie kommt.

Ist "Auth session missing!" dasselbe wie "invalid claim: missing sub claim"?

Nein. "Auth session missing!" wird in deiner App entschieden, bevor irgendeine Anfrage sie verlässt. "invalid claim: missing sub claim" kommt von Supabase Auth zurück, das eine Anfrage bekommen und dort, wo das Token einer Person hingehört, den Schlüssel deines Projekts gefunden hat. Das Erste heißt, es gab nichts zu schicken. Das Zweite heißt, es wurde das Falsche geschickt.

Soll ich getSession() statt getUser() verwenden?

Nicht, um diesen Fehler loszuwerden. Im Browser findet getSession() denselben leeren Speicher und gibt keine Sitzung zurück, also ändert sich nichts, außer dass die Fehlermeldung verschwindet. Auf einem Server ist es schlimmer: supabase-js dokumentiert, dass getSession() die Sitzung direkt aus dem Speicher liest, etwa aus Cookies der Anfrage, die nicht echt sein müssen, und empfiehlt stattdessen getUser(), weil es bei Supabase Auth nachfragt.

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.