Zum Inhalt springen

Sicherheitsgrundlagen

API-Schlüssel verstecken: ab in eine Supabase Edge Function

Einen API-Schlüssel verstecken heißt, ihn aus dem Browser zu nehmen. Eine Supabase Edge Function ist der kleinste Ort dafür, und zwei Schritte zählen mehr.

Vlad Tkachenko11 Min. Lesezeit
Eine Seite mit einem Loch in Schlüsselform, dahinter eine versiegelte Kiste mit Schloss und dem Schlüssel hinter einem Fenster.

Kurz gesagt

  • Nichts im Browser kann ein Geheimnis behalten. Einen API-Schlüssel verstecken heißt also, ihn dorthin zu verschieben, wo das geht. Eine Supabase Edge Function ist der kleinste Server, den du bekommst.
  • Der Umzug sind vier Schritte. Tausche den bereits veröffentlichten Schlüssel vorher aus, denn die Kopie in deinem alten Bundle bleibt lesbar, auch nachdem du ihn herausgenommen hast.
  • Eine deployte Function verlangt von Haus aus ein Token, und der öffentliche Schlüssel aus deinem eigenen Frontend erfüllt diese Forderung. Zu prüfen, wer da ruft, ist ein eigener Schritt, und dieser hält Fremde von deinem Kontingent fern.

Jeder Beitrag über einen geleakten API-Schlüssel endet an derselben Stelle: ab damit auf einen Server. Dann hört er auf. Zurück bleibst du mit einer App, die du in Lovable, Bolt oder Cursor gebaut hast, keinem Server weit und breit und einem Satz, der voraussetzt, dass du schon weißt, was als Nächstes zu tun ist.

Das hier lässt ein Beitrag nach dem anderen weg. Der Schlüssel in einer Supabase Edge Function ist aus deiner Seite verschwunden, und das allein hält niemanden davon ab, ihn zu benutzen. Supabase schreibt selbst, warum: Die Prüfung, die eine deployte Function von Haus aus durchläuft, akzeptiert auch deinen öffentlichen Schlüssel, und dein öffentlicher Schlüssel steht in deinem Frontend, wo ihn jeder kopieren kann. Den Schlüssel zu verstecken ist die einfache Hälfte. Über die andere schreibt niemand: zu entscheiden, wem deine neue Function antwortet.

Stell dir den Schlüssel als Generalschlüssel zu einem Lager vor. Im Moment klebt er innen am Schaufenster, wo ihn jeder lesen kann, der stehen bleibt. Eine Edge Function ist ein Hinterzimmer mit Durchreiche: Der Schlüssel kommt dort hinein, und Besucher fragen durch die Luke, statt hereinzukommen und sich zu bedienen. Ob das eine Verbesserung ist, hängt davon ab, für wen sich die Luke öffnet.

Wohin gehört mein API-Schlüssel, wenn nicht ins Frontend?

Überall dorthin, wo kein Browser ist. Eine Edge Function ist die kleinste Variante davon.

Ein Browser kann kein Geheimnis behalten. Alles, was deine Seite zum Laufen braucht, wird von jedem Besucher heruntergeladen, und ein Besucher kann alles davon lesen: den Code, die Bilder, die Werte, die in den Code kompiliert wurden. Das ist kein Fehler deines Baukastens. Es ist das, was eine Webseite ist. Welche API-Schlüssel im Frontend sicher sind ist die lange Fassung; die kurze lautet, dass ein sk_-Schlüssel, ein OpenAI-Schlüssel oder ein geheimer Supabase-Schlüssel es im Browser nie überlebt hätte.

Eine Supabase Edge Function ist ein kleines Stück Code, das auf Supabase-Maschinen läuft. Sie kann Secrets lesen, die du in deinem Projekt gesetzt hast, sie antwortet unter einer eigenen Webadresse, und deine App ruft sie beim Namen auf. Du schreibst eine Datei, Supabase führt sie aus, und es gibt keinen Server, den du mieten, patchen oder wachhalten müsstest.

Oben: Der Schlüssel liegt in der Datei, die jeder Besucher herunterlädt, also hat ihn jeder Besucher. Unten: Der Browser fragt die Function, und der Schlüssel verlässt Supabase nie.

Tausche den veröffentlichten Schlüssel aus, bevor du irgendetwas verschiebst

Der Schlüssel, der heute in deinem Frontend steht, ist bereits öffentlich, und er bleibt es, nachdem du ihn aus dem Code genommen hast.

Jeder Besucher, der deine Seite geladen hat, hat ihn heruntergeladen. Jeder Crawler auch, und es laufen automatische durch das ganze Web, die genau nach solchen Zeichenketten suchen. Dein altes Bundle steckt außerdem noch in deiner Versionsgeschichte und in jedem Cache, der eine Kopie deiner Seite hat. Eine Zeile aus einer Datei zu löschen, die dir gehört, ändert nichts an einem Wert, der längst ausgehändigt wurde.

Die Reihenfolge ist also: beim Anbieter einen neuen Schlüssel erzeugen, ihn einen Moment behalten und den alten widerrufen, sobald die Function unten läuft. Lies danach Abrechnung und Nutzungsprotokolle für den ganzen Zeitraum, in dem der alte Schlüssel draußen war. Ein Tausch stoppt, was als Nächstes passiert; auf das bereits Geschehene hat er keinen Einfluss. War es ein geheimer Supabase-Schlüssel, hat die richtige Reihenfolge einen Haken, den du vorher kennen solltest.

Die vier Schritte

Secret ablegen, Function schreiben, deployen, dann deine App umstellen, damit sie die Function statt des Anbieters aufruft.

SchrittAuf der KommandozeileIm Dashboard
1. Secret ablegensupabase secrets set MY_API_KEY=…Edge Functions → Secrets, dann Key und Value
2. Function schreibensupabase functions new forward-requestEdge Functions → Deploy a new function → Via Editor
3. Deployensupabase functions deployDer Deploy-Knopf unter dem Editor
4. Aus der App aufrufensupabase.functions.invoke('…')Dasselbe, im Code deiner App

In der Function kommt das Secret als Umgebungsvariable an, also als benannter Wert, den der Code lesen kann und niemand von außen. Die Supabase-Doku zu Edge Function Secrets hat die vollständige Referenz, und drei Details darin sparen eine verwirrende Stunde:

  • Ein Secret-Name darf nicht mit SUPABASE_ beginnen. Dieses Präfix ist für die Werte reserviert, die Supabase für dich setzt, und Dashboard wie API weisen es ab.
  • Ein neues Secret ist sofort lesbar. Du musst die Function danach nicht neu deployen.
  • Lokal und Produktion sind getrennt. Der lokale Stack liest supabase/functions/.env, was ein anderer Ort ist als die Secrets deines laufenden Projekts. Setze den Wert also an beiden Stellen, sonst läuft die Function auf deinem Rechner und scheitert nach dem Deploy.

Lösche danach die alte Variable aus deinem Frontend. Hieß sie VITE_, NEXT_PUBLIC_ oder EXPO_PUBLIC_, war dieses Präfix die Anweisung, den Wert in das Bundle zu kompilieren, und das Präfix ist die ganze Geschichte dahinter.

Heißt eine Token-Pflicht, dass nur meine Nutzer aufrufen können?

Nein, und das ist die eine Stelle in diesem Beitrag, die zweimal zu lesen lohnt.

Supabase schaltet von Haus aus eine Prüfung namens verify_jwt ein. Sie sieht sich den Authorization-Header an, bevor dein Code läuft, und weist die Anfrage ab, wenn dort nichts Gültiges steht. Das klingt nach einer Tür, die nur deine Nutzer öffnen. Das ist sie nicht, denn Supabase dokumentiert ebenso, dass dieselbe Prüfung einen öffentlichen oder geheimen Schlüssel akzeptiert und zwar auf beiden Headern, aus Kompatibilität mit älteren Projekten, und sagt klar dazu, dass die Prüfung allein einen Aufrufer nicht authentifiziert, der nur einen API-Schlüssel schickt.

Dein öffentlicher Schlüssel steht in deinem Frontend. Das ist richtig so und genau dafür gedacht. Es heißt aber auch: Wer deine Seite öffnet, den Schlüssel liest und ihn an deine neue Function schickt, kommt an der Plattformprüfung vorbei und in deinen Code hinein.

Im Lager ist verify_jwt ein Pförtner, der prüft, ob du einen Besucherausweis dabeihast. Den hat jeder Besucher, weil du sie an der Tür ausgibst. Die Person an der Durchreiche hat eine andere Aufgabe, und die fragt, welcher Besucher du bist.

Beide Aufrufer kommen durch die Plattformprüfung. Nur der zweite kommt durch eine Prüfung, wer da ruft, und genau die musst du selbst hinzufügen.

Die Function abriegeln, damit kein offener Proxy daraus wird

Entscheide, welche Aufrufer deine Function akzeptiert, und schreibe diese Entscheidung in die Function.

Supabase liefert dafür einen Wrapper mit, es ist also eine Zeile und kein Projekt. Setze auth: 'user', und die Function nimmt das Token eines angemeldeten Nutzers an und gibt deinem Code einen Datenbank-Client, der bereits auf dessen Row-Level-Security-Regeln eingegrenzt ist:

import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    // ctx.userClaims ist, wer da ruft. Weise hier ab, was du nicht willst,
    // und rufe dann den Anbieter mit dem Secret aus der Umgebung auf.
    return Response.json({ ok: true })
  }),
}

Die Seite Securing Edge Functions listet die anderen Modi, und zwei davon sind für gewöhnliche Apps wichtig. Eine Function, die von einer anderen Maschine statt von einem Browser aufgerufen wird, nimmt auth: 'secret', mit dem geheimen Schlüssel im apikey-Header. Eine Function, die Webhooks von Stripe oder GitHub empfängt, kann keines von beidem nehmen, weil diese Anbieter kein Token von dir haben; sie setzt verify_jwt = false und prüft stattdessen die Signatur des Anbieters im Handler.

Was auch immer du wählst, entscheide bewusst, was passiert, wenn ein Aufrufer auftaucht, mit dem du nicht gerechnet hast. Supabase nennt als Beispiel für eine Function, die jeden annehmen darf, einen Health-Check, und ein Health-Check kostet nichts, wenn ein Fremder ihn aufruft. Eine Function, die einen bezahlten API-Aufruf weiterreicht, stellt dir jeden einzelnen in Rechnung.

Darf ich service_role in einer Edge Function benutzen?

Ja. Eine Function ist der eine Ort, an den ein geheimer Supabase-Schlüssel gehört.

Einfügen musst du ihn auch nicht. Supabase legt die Projektschlüssel in die Umgebung der Function, der Code liest sie also von dort und nicht aus einem Secret, das du gesetzt hast. Neuere Projekte bekommen SUPABASE_SECRET_KEYS und SUPABASE_PUBLISHABLE_KEYS, jeweils ein kleines Verzeichnis benannter Schlüssel; ältere Projekte bekommen SUPABASE_SERVICE_ROLE_KEY und SUPABASE_ANON_KEY unter ihren ursprünglichen Namen. Was sich mit den neuen Supabase-Schlüsseln geändert hat sagt dir, welches Paar du hast.

Nimm den geheimen Schlüssel für Arbeit, die wirklich jede Zeile sehen muss, etwa einen Prüfeintrag, den der Nutzer nicht bearbeiten darf. Für alles, was ein Nutzer über sich selbst liest, nimm sein eigenes Token und lass deine Row-Level-Security-Regeln filtern, wofür sie da sind.

Im Browser nachsehen, der einzige echte Beweis

Lade deine laufende Seite, öffne den Netzwerk-Tab und sieh dir an, was dein Browser tatsächlich sendet.

Zwei Dinge zum Prüfen:

  • Keine Anfrage trägt den Schlüssel. Klick dich durch den Teil deiner App, der früher den Anbieter aufgerufen hat. Die ausgehende Anfrage sollte an …supabase.co/functions/v1/deine-function gehen, und das einzige Merkmal darin sollte dein öffentlicher Schlüssel oder das Token deines Nutzers sein.
  • Der Schlüssel steht nicht im heruntergeladenen Code. Nutze die Suche deines Browsers über alle geladenen Dateien und füge das erste Dutzend Zeichen des alten Schlüssels ein. Kein Treffer ist die Antwort, die du willst.

Erst ein neuer Build und ein neues Deploy nehmen einen Wert aus dem Bundle. Ein Schlüssel, der in der Suche noch auftaucht, heißt deshalb meistens, dass das Frontend raus war, bevor die Variable heraus war.

Unser kostenloser Scan macht die zweite Prüfung von außen und nennt, was er in deinem laufenden Bundle lesen kann, auch welchen Supabase-Schlüssel du ausgeliefert hast. Er dauert rund 20 Sekunden und braucht kein Konto: App scannen.

So geht es aus Lovable, Bolt oder Replit heraus

Nimm das Supabase-Dashboard. Darin kommt kein Terminal vor.

Öffne dein Projekt, wähle in der Seitenleiste Edge Functions und dann Deploy a new function → Via Editor. Supabase' Dashboard-Schnelleinstieg führt mit Screenshots hindurch, und unter den angebotenen Vorlagen ist eine für das Weiterreichen an einen KI-Anbieter, also genau die Form dieser Aufgabe. Das Deployen dauert zwischen zehn und dreißig Sekunden, danach läuft die Function unter einer eigenen Adresse. Dein Secret kommt im selben Bereich auf die Seite Edge Function Secrets.

Ein Hinweis, bevor du dich darauf verlässt: Supabase schreibt, dass der Dashboard-Editor keine Versionsverwaltung, keine Versionen und kein Zurückrollen kennt, und empfiehlt ihn für schnelle Arbeit statt für Code, der bleiben soll. Ein Druck auf Deploy überschreibt, was da war. Wenn die Function am Ende etwas tut, das dir wichtig ist, lade sie auf dieser Seite herunter und bewahre die Datei auf.

Für Replit gibt es eine eigene Fassung dieser Frage, weil Replit einen eigenen Secrets-Speicher hat und deine App dort ihren eigenen Server betreibt. Was Replit Secrets wirklich tun ist diese Fassung.

Wann eine Edge Function die falsche Antwort ist

Zwei Fälle, und in beiden kostet der Umzug Arbeit und bringt nichts.

Der Schlüssel war öffentlich gedacht. Ein Stripe-Schlüssel mit pk_live_, ein öffentlicher Supabase-Schlüssel oder ein anon-Schlüssel, ein öffentliches Mapbox-Token: Die gehören in einen Browser, und der Schutz sitzt woanders. Einen davon hinter eine Function zu legen fügt einen Umweg hinzu und nimmt kein Risiko weg. Welche Schlüssel das sind ist die Liste.

Der Schlüssel lässt sich auf deine eigene Seite beschränken. Googles Browser-Schlüssel sind das übliche Beispiel: In der Google-Konsole kannst du einen auf deine Domains begrenzen, und genau das ist die Lösung, die Google für diesen Fall vorsieht. Der Schlüssel bleibt lesbar und nützt niemandem mehr, der ihn kopiert.

Alles andere gehört auf einen Server. Von einem OpenAI- oder Anthropic-Schlüssel gibt es keine öffentliche Variante, weshalb ein KI-Schlüssel in deinem Frontend durch keine Einstellung zu retten ist, und kein Minifizieren versteckt einen geheimen Stripe-Schlüssel vor jemandem, der deine Seite liest.

Was du jetzt tun kannst

Was zu tun ist

  • Tausche zuerst den Schlüssel. Erzeuge beim Anbieter einen neuen, lege ihn als Secret der Function ab und widerrufe den alten, sobald die Function läuft.
  • Lege das Secret in deinem Supabase-Projekt ab, nicht im Repository und nicht in einer VITE_-Variablen. Setze es für lokale Entwicklung und für Produktion getrennt.
  • Deploye eine Function, die das Secret benutzt, und stelle deine App so um, dass sie die Function beim Namen aufruft statt den Anbieter.
  • Füge eine Prüfung hinzu, wer da ruft. verify_jwt allein akzeptiert den öffentlichen Schlüssel aus deinem eigenen Frontend und hält damit keinen Fremden ab.
  • Lösche die alte Variable, baue neu, und prüfe im Netzwerk-Tab deines Browsers, dass nichts Ausgehendes den Schlüssel trägt.
  • Lies Abrechnung und Nutzung für den ganzen Zeitraum, in dem der alte Schlüssel öffentlich war. Ein Tausch stoppt die nächste Buchung und nicht die letzte.

Wenn du lieber deine ganze App durchgehst statt eines einzelnen Schlüssels: Die Sicherheits-Checkliste in 10 Minuten deckt das hier zusammen mit den anderen Dingen ab, die in einer frisch gestarteten App zu schließen sind.

FAQ

Wohin gehört mein API-Schlüssel, wenn nicht ins Frontend?

Überall dorthin, wo kein Browser ist. Eine Supabase Edge Function ist die kleinste Möglichkeit: Du legst den Schlüssel als Secret in deinem Projekt ab, schreibst eine kleine Datei, die ihn benutzt, und deine App ruft diese Function beim Namen auf, statt den Anbieter direkt anzusprechen. Genauso funktionieren eine Serverless-Route bei deinem Hoster oder ein eigener Server. Gemeinsam ist ihnen, dass der Schlüssel dort gelesen wird, wo dein Besucher ihn nicht sieht.

Was ist eine Supabase Edge Function?

Ein kleines Stück Code, das auf Supabase-Maschinen läuft statt im Browser deines Besuchers. Es antwortet unter einer eigenen Webadresse, es kann Secrets lesen, die du in deinem Projekt gesetzt hast, und deine App ruft es mit supabase.functions.invoke auf. Du schreibst eine Datei, Supabase führt sie aus, und es gibt keinen Server, den du mieten oder pflegen müsstest.

Muss ich meinen Schlüssel nach dem Umzug austauschen?

Ja, und zwar zuerst. Der Schlüssel aus deinem Frontend wurde von jedem Besucher und von jedem Crawler heruntergeladen, der deine Seite gelesen hat, und ihn aus dem Code zu nehmen ändert an diesen Kopien nichts. Erzeuge beim Anbieter einen neuen Schlüssel, lege ihn als Secret deiner Edge Function ab und widerrufe den alten. Sieh dir danach Abrechnung und Nutzung für den ganzen Zeitraum an, in dem der alte Schlüssel gültig war.

Kann jeder meine Edge Function aufrufen?

Von Haus aus weist eine Function eine Anfrage ohne jedes Token ab, aber diese Prüfung ist schwächer, als sie klingt. Supabase dokumentiert, dass die Plattformprüfung auch deinen öffentlichen oder geheimen Schlüssel akzeptiert, und dein öffentlicher Schlüssel steht in deinem Frontend, wo ihn jeder lesen kann. Die Prüfung stoppt also eine leere Anfrage und keine entschlossene. Wenn nur deine angemeldeten Nutzer durchkommen sollen, prüfe den Aufrufer in der Function selbst.

Darf ich service_role in einer Edge Function benutzen?

Ja. Eine Edge Function ist der eine Ort, an den ein geheimer Supabase-Schlüssel gehört, und Supabase legt die Projektschlüssel für dich in die Umgebung der Function, sodass du nie einen einfügen musst. Nimm ihn für Arbeit, die jede Zeile sehen muss, und nimm das Token des Aufrufers für alles, was sich an deine Row-Level-Security-Regeln halten soll.

Geht das auch ohne Terminal?

Ja. Das Supabase-Dashboard hat einen Bereich Edge Functions, in dem du eine Function im Browser schreibst, auf Deploy drückst und dein Secret auf der Seite Edge Function Secrets setzt. Supabase weist darauf hin, dass der Dashboard-Editor keine Versionsgeschichte führt, und empfiehlt ihn deshalb für schnelle Arbeit und die Kommandozeile für alles, was bleiben soll.

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

Unsicher, wie es um deine eigene App steht?

Starte einen kostenlosen Scan und erhalte in rund 20 Sekunden eine verständliche Note von A bis F. Ohne Konto, ohne Karte.

App kostenlos scannen

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