Supabase RLS mit Keycloak: die Architektur für dein Kundenportal

Keycloak beantwortet, wer dein Nutzer ist; Postgres mit Row Level Security entscheidet in der Datenbank, welche Zeilen er sieht. Das Bindeglied ist der JWT von Supabase Auth, angereichert per Custom Access Token Hook. Dieser Artikel zeigt dir die Architektur, die drei Anbindungswege und vier Fallstricke aus unseren Kundenportal-Projekten.
8 Min. LesezeitMatthias RadscheitMatthias Radscheit
Happycodingde-DE

TL;DR

Keycloak beantwortet, wer dein Nutzer ist; Postgres mit Row Level Security entscheidet in der Datenbank, welche Zeilen er sieht. Das Bindeglied ist der JWT von Supabase Auth, angereichert per Custom Access Token Hook. Dieser Artikel zeigt dir die Architektur, die drei Anbindungswege und vier Fallstricke aus unseren Kundenportal-Projekten.

  • Trenne Authentifizierung und Autorisierung: Keycloak prüft die Identität, RLS-Policies in Postgres entscheiden pro Zeile über den Datenzugriff.
  • Supabase Third-Party Auth unterstützt Keycloak nicht (nur Clerk, Firebase, Auth0, Cognito, WorkOS) – der offizielle Weg führt über Supabase Auth als Token-Aussteller.
  • Autorisierungsdaten gehören in app_metadata oder in Claims aus dem Custom Access Token Hook – user_metadata kann der Nutzer selbst ändern.
  • Keycloak-Logout beendet die Supabase-Session nicht: Der JWT gilt standardmäßig eine Stunde weiter, Rechteentzug greift erst beim nächsten Token.
  • Lege einen Index auf jede Spalte, auf die eine Policy filtert, und nutze die (select auth.uid())-Schreibweise – sonst skaliert dein Portal nicht.

Ein Portal, zwei Fragen: Wer bist du, was darfst du sehen?

Jedes B2B-Kundenportal beantwortet bei jedem Klick zwei Fragen: Wer bist du? Und welche Daten darfst du sehen? Die erste ist Authentifizierung, die zweite Autorisierung. In vielen Projekten erledigt beides dieselbe Schicht, nämlich der Applikationscode. Genau dort entstehen die Lecks, bei denen Mandant A plötzlich die Aufträge von Mandant B sieht.

Meine Antwort für Portale mit mehreren Firmenkunden: Trenne die beiden Fragen architektonisch. Ein externes IAM beantwortet die Identitätsfrage, Row Level Security (RLS) in Postgres die Zugriffsfrage, und zwar pro Tabellenzeile direkt in der Datenbank. Dieser Artikel zeigt dir die Architektur dahinter: die Bausteine, die Anbindung, die Policies und vier Fallstricke aus unserer Projektpraxis.

Technisches Verständnis setze ich dabei voraus: Du musst kein SQL schreiben können, aber wissen wollen, warum dein Dienstleister die Zugriffskontrolle in die Datenbank legt. Alle Preis- und Versionsangaben sind datiert, alle Aussagen zur Mechanik stammen aus den offiziellen Dokumentationen von Supabase und Keycloak.

Die Architektur in Worten: Keycloak vorn, Postgres hinten, JWT dazwischen

Das Architekturbild hat drei Schichten. Vorn steht Keycloak als Identity Provider: Es führt das Nutzerverzeichnis, rendert den Login, spricht OIDC und SAML und liefert MFA bis hin zu Passkeys. Keycloak ist quelloffen, seit April 2023 CNCF-Incubating-Projekt und steht aktuell bei Version 26.8.0 (Stand: Oktober 2026).

Dahinter liegt Supabase als Daten-Layer: gemanagtes Postgres mit automatisch erzeugter API. Jede Tabelle deines Portals trägt RLS-Policies, also SQL-Regeln, die bei jeder Abfrage pro Zeile entscheiden, ob der anfragende Nutzer sie sehen darf. Die Autorisierung wohnt damit in der Datenbank statt im Applikationscode.

Betrieblich heißt diese Aufteilung: Supabase betreibt die Datenbank für dich, Keycloak betreibst du selbst oder lässt es betreiben. Zwei Komponenten, klar getrennte Zuständigkeiten: Ein Keycloak-Update fasst deine Daten nicht an, eine Datenbankmigration nicht deinen Login.

Das Bindeglied ist der JSON Web Token (JWT): ein signierter Ausweis über den angemeldeten Nutzer samt seiner Claims, etwa Nutzer-ID, Rollen und Mandant. Der Ablauf bei jedem Login:

  • Dein Portal schickt den Nutzer zum Keycloak-Login, auf Wunsch per SSO ins Firmenverzeichnis deines Kunden.
  • Keycloak bestätigt die Identität; Supabase Auth tauscht den Autorisierungscode gegen eine Session.
  • Supabase Auth stellt den JWT aus, den dein Frontend fortan bei jeder API-Anfrage mitschickt.
  • Postgres liest die Claims aus dem JWT und wertet jede RLS-Policy dagegen aus.

Der Gewinn dieser Anordnung: Selbst wenn eine API-Route einen Filter vergisst, gibt die Datenbank fremde Mandantendaten nicht heraus. RLS ist die zweite Verteidigungslinie – sie hält auch dann, wenn die erste wackelt.

Wie Supabase externe Identitäten akzeptiert: drei Wege, zwei führen zu Keycloak

Vorab die Frage, die mir in Erstgesprächen am häufigsten begegnet: «Nimmt Supabase einfach die Tokens von Keycloak an?» Die kurze Antwort: nicht direkt. Die Supabase-API vertraut nur JWTs von Ausstellern, die du ausdrücklich konfiguriert hast. Dafür gibt es drei Wege – und nur zwei davon führen zu Keycloak.

Weg 1: der eingebaute Keycloak-Provider

Supabase Auth führt Keycloak als offiziellen Login-Provider. Du legst in Keycloak einen OIDC-Client mit Zugriffstyp «confidential» an, trägst als Redirect-URI die Callback-Adresse deines Supabase-Projekts ein und hinterlegst Client-ID und Secret im Dashboard. Seit Keycloak 22 muss dein Frontend beim Login zusätzlich den Scope openid mitgeben; der Aufruf selbst ist ein Einzeiler mit signInWithOAuth.

Weg 2: Custom-OIDC-Provider für Sonderfälle

Seit Mai 2026 bindet Supabase Auth zusätzlich beliebige standardkonforme OIDC-Provider an. Du hinterlegst nur die Issuer-URL deines Keycloak-Realms; Endpunkte und Signaturschlüssel findet Supabase über das Discovery-Dokument selbst. Dieser Weg lohnt sich, wenn du mehrere Realms betreibst oder Einstellungen brauchst, die der eingebaute Keycloak-Connector nicht abbildet.

Weg 3: Third-Party Auth – für Keycloak nicht vorgesehen

Supabase kennt außerdem Third-Party Auth: Dabei akzeptiert die Daten-API die JWTs eines fremden IAM direkt, Supabase Auth tritt ab. Offiziell unterstützt sind dafür genau fünf Anbieter: Clerk, Firebase Auth, Auth0, AWS Cognito und WorkOS (Stand: Oktober 2026). Keycloak steht nicht auf der Liste. Wer dir eine «direkte» Keycloak-Anbindung an die Daten-API verspricht, verlässt also den offiziell unterstützten Pfad.

Technisch verlangt Supabase für diesen dritten Weg asymmetrisch signierte JWTs mit einem kid-Header, über den der passende Signaturschlüssel gefunden wird. Das erklärt auch die kurze Liste: Die Daten-API kann fremde Tokens nur prüfen, niemals selbst ausstellen, und dieses Vertrauen vergibt Supabase bislang nur an namentlich integrierte Anbieter.

Preislich schlägt dieser dritte Weg laut Supabase-Preisliste mit 0,00325 US-Dollar pro Third-Party-MAU oberhalb des Plan-Kontingents zu Buche (Stand: Oktober 2026). Für die Übersicht:

WegToken-AusstellerKeycloak?Typischer Einsatz
Eingebauter Keycloak-ProviderSupabase AuthJa, offiziell dokumentiertStandardfall: ein Realm, ein Portal
Custom-OIDC-Provider (seit Mai 2026)Supabase AuthJa, generisch per Issuer-URLMehrere Realms, Sonderkonfigurationen
Third-Party AuthExternes IAMNeinBestands-IAM bei Clerk, Auth0, Firebase, Cognito oder WorkOS

Merksatz: Den JWT, den deine Datenbank prüft, stellt bei einer Keycloak-Anbindung immer Supabase Auth aus. Keycloak steht vor der Tür, nicht neben der Datenbank.

RLS-Policies auf JWT-Claims: Autorisierung, die in der Datenbank wohnt

Damit zur zweiten Hälfte der Architektur, den Policies. Supabase stellt dir in Postgres zwei Funktionen bereit, mit denen eine Policy den Token des Anfragenden lesen kann.

auth.uid() und auth.jwt(): die zwei Lesegeräte

auth.uid() liefert die Nutzer-ID aus dem JWT, auth.jwt() den kompletten Token als JSON. Eine Mandanten-Policy sieht damit so aus: using ((select auth.jwt() -> 'app_metadata' ->> 'tenant_id') = tenant_id). Jede Zeile trägt ihre Mandanten-ID, und nur Zeilen des eigenen Mandanten passieren den Filter.

Nach demselben Muster bildest du Rollen ab: Eine Policy für Schreibzugriffe prüft etwa (select auth.jwt() -> 'app_metadata' ->> 'portal_role') = 'admin', während Lesezugriffe allen Mitgliedern des Mandanten offenstehen. So entsteht aus wenigen Zeilen SQL ein Rechtemodell, das für jeden Zugriffsweg gilt: Portal-Frontend, Admin-Dashboard und jede künftige Anwendung auf derselben Datenbank.

Zwei Details aus der Supabase-Dokumentation solltest du von Anfang an beherzigen. Erstens: Ohne angemeldeten Nutzer liefert auth.uid() schlicht null, deshalb gehört ein expliziter Null-Check in jede Policy. Zweitens: Die Schreibweise (select auth.uid()) lässt Postgres das Ergebnis pro Abfrage zwischenspeichern, statt es pro Zeile neu auszuwerten.

app_metadata statt user_metadata

Der Token transportiert zwei Metadaten-Töpfe, und die Unterscheidung ist sicherheitskritisch. user_metadata kann der angemeldete Nutzer per supabase.auth.update() selbst ändern; die Supabase-Doku nennt es ausdrücklich keinen guten Ort für Autorisierungsdaten. Wer dort Rollen ablegt, erlaubt die Selbstbeförderung zum Admin. Rollen und Mandanten-IDs gehören in app_metadata: Dieses Feld ist nur serverseitig beschreibbar.

Der Custom Access Token Hook

Bleibt die Frage, wie Keycloak-Wissen in den Supabase-JWT kommt. Das Werkzeug dafür heißt Custom Access Token Hook: eine Postgres-Funktion oder ein HTTP-Endpunkt, den Supabase Auth vor jeder Token-Ausstellung aufruft und der eigene Claims ergänzen darf. Pflicht-Claims wie sub, exp und role darf der Hook nicht entfernen, sonst verweigert Supabase den Token.

In unseren Portalprojekten liest dieser Hook die Rollen- und Mandanten-Zuordnung aus einer eigenen Tabelle und schreibt sie als Claims in den Token. So liegt die Wahrheit über Berechtigungen an genau einem Ort, den kein Nutzer beschreiben kann.

Vier Fallstricke aus unseren Portal-Projekten

Was jetzt kommt, steht so in keiner Dokumentation: Es ist Projekterfahrung aus Kundenportalen, die ich mit happycoding auf diesem Stack gebaut habe. Vier Punkte haben uns real Zeit gekostet.

1. Zwei Session-Welten: Keycloak-Logout beendet die Supabase-Session nicht

Keycloak und Supabase führen getrennte Sessions. Sperrt dein Kunde einen Mitarbeiter in seinem Verzeichnis oder meldet der sich in Keycloak ab, läuft die Supabase-Session zunächst weiter: Der Access Token gilt laut Supabase-Doku standardmäßig eine Stunde, und Refresh Tokens verlängern die Session darüber hinaus. Entzogene Rechte kommen erst mit dem nächsten frisch ausgestellten Token an.

Unser Umgang damit: die Token-Laufzeit bewusst wählen (von Werten unter fünf Minuten rät die Supabase-Doku ab) und harte Sperrungen zusätzlich serverseitig prüfen. Beim Offboarding eines Mitarbeiters genügt «irgendwann in der nächsten Stunde» zuweilen nicht.

2. Claim-Mapping: Rollen reisen nicht von allein

Unsere Erwartung im ersten Projekt: Nach dem Login stehen die Keycloak-Rollen einfach im Supabase-JWT. Die Realität: Was der Login-Provider über den Nutzer liefert, wird nicht automatisch zum vertrauenswürdigen Autorisierungs-Claim. Die Überführung baust du selbst, sauber über app_metadata oder den Access-Token-Hook. Plane dieses Mapping von Anfang an ein; es ist das eigentliche Integrationsstück dieses Stacks. In unseren Projekten lagen dafür je nach Rollenmodell ein bis zwei Entwicklungstage an, Tests eingerechnet.

3. Realm-Rollen vs. Client-Rollen: zwei Orte im Token

Keycloak kennt realm-weite Rollen und Client-Rollen als eigenen Namensraum pro Anwendung. Im Keycloak-Token landen sie an verschiedenen Stellen (realm_access beziehungsweise resource_access), und Protocol Mapper bestimmen pro Client, welche Claims überhaupt hineingeschrieben werden. Uns hat ein Umbau von Realm- auf Client-Rollen einmal still das Mapping zerlegt: Die Policies griffen ins Leere, und Nutzer sahen schlicht keine Daten mehr.

Die Lehre daraus: Lege die Rollen-Taxonomie fest, bevor die erste Policy entsteht, und teste deine Policies automatisiert gegen echte Tokens. Ein RLS-Fehler wirft keine Fehlermeldung, er liefert nur leere Ergebnisse.

4. RLS-Performance: Policies laufen pro Zeile

Postgres wertet eine Policy gegen jede Kandidaten-Zeile aus. Ohne Index auf der Mandanten-Spalte wird aus jeder Portal-Abfrage ein Sequential Scan, der mit dem Datenbestand wächst. In einem unserer Projekte fiel das erst auf, als ein neuer Kunde mit einem Vielfachen der bisherigen Datenmenge startete. Seitdem gilt bei uns: ein Index auf jede Spalte, auf die eine Policy filtert, und die select-Schreibweise ab der ersten Migration.

Wann dieser Stack passt und wann nicht

Zum Schluss die ehrliche Einordnung, denn diese Architektur ist kein Standardrezept für jedes Projekt. Sie passt, wenn dein Portal diese Merkmale hat:

  • Mehrere Mandanten: Firmenkunden mit strikt getrennten Daten, bei denen ein Leck Vertragsfolgen hätte.
  • SSO-Wunsch: Deine Kunden wollen sich mit ihrem eigenen Entra ID oder LDAP anmelden; genau dafür ist Keycloak gebaut.
  • Datenstandort: Du willst den Identity Provider selbst in der EU betreiben, statt Identitäten an einen US-SaaS zu geben.
  • Moderne Anmeldung: MFA und Passkeys sollen Konfiguration sein, kein Eigenbau.

Und wann rate ich ab? Für Consumer-Apps mit simplem E-Mail- oder Social-Login ist Keycloak Overhead: Dort genügt Supabase Auth allein, der Pro-Plan deckt für 25 US-Dollar im Monat bereits 100.000 aktive Nutzer ab (Stand: Oktober 2026). Auch den Betrieb solltest du ehrlich einpreisen: Keycloak liefert vier Minor-Releases pro Jahr, die jemand einspielen und testen muss.

Die vollständige Kostenrechnung samt meiner grundsätzlichen Empfehlung findest du im Stack-Artikel zu Supabase und Keycloak: Diesen Teil wiederhole ich hier bewusst nicht.

Nächste Schritte

Wenn du ein Kundenportal mit Mandantentrennung planst, kläre zuerst drei Dinge: Brauchen deine Kunden SSO in ihr eigenes Verzeichnis? Wie viele Mandanten starten im ersten Jahr? Und wer betreibt den Identity Provider? Mit diesen drei Antworten steht die Architekturentscheidung meist nach einem Gespräch.

Wie wir solche Projekte aufsetzen, liest du auf der Seite Kundenportal entwickeln lassen. Oder du überspringst das Lesen: In einem kostenlosen Erstgespräch gehen wir deine Anforderungen durch und skizzieren die Architektur für deinen Fall. Buch dir direkt einen Termin.

Häufige Fragen

Kann Supabase Keycloak-JWTs direkt akzeptieren?
Nicht über den offiziellen Third-Party-Auth-Weg: Der unterstützt Stand Oktober 2026 nur Clerk, Firebase Auth, Auth0, AWS Cognito und WorkOS. Keycloak bindest du stattdessen als Login-Provider in Supabase Auth an – über den eingebauten Keycloak-Provider oder seit Mai 2026 als Custom-OIDC-Provider. Den JWT für die Datenbank stellt dann Supabase Auth aus.
Wie kommen Keycloak-Rollen in meine RLS-Policies?
Über den Custom Access Token Hook: eine Postgres-Funktion, die Supabase Auth vor jeder Token-Ausstellung aufruft. Dort schreibst du Rollen und Mandanten-ID als Claims in den Token, deine Policies lesen sie anschließend mit auth.jwt(). Wichtig: Autorisierungsdaten nie aus user_metadata lesen, denn dieses Feld kann der Nutzer selbst ändern.
Was passiert, wenn ich einem Nutzer in Keycloak die Rechte entziehe?
Zunächst nichts: Die Supabase-Session läuft weiter, der Access Token gilt standardmäßig eine Stunde. Erst beim nächsten Token-Refresh oder Login greifen die neuen Rechte. Für harte Sperrungen brauchst du deshalb eine kürzere Token-Laufzeit oder eine zusätzliche serverseitige Prüfung an den kritischen Stellen.
Macht RLS meine Datenbank langsam?
Nur bei schlampiger Umsetzung. Postgres wertet Policies pro Zeile aus. Mit zwei Handgriffen bleibt das schnell: Funktionen wie auth.uid() in ein select wrappen, damit Postgres das Ergebnis pro Abfrage zwischenspeichert, und ein Index auf jede Spalte, auf die eine Policy filtert.
Reicht nicht Supabase Auth allein, ohne Keycloak?
Für Consumer-Apps mit E-Mail- oder Social-Login: ja, häufig. Keycloak lohnt sich, sobald Firmenkunden SSO über ihr eigenes Verzeichnis erwarten, du mehrere Anwendungen an eine Identität hängen willst oder Compliance-Vorgaben einen selbst betriebenen Identity Provider in der EU verlangen.
Was kostet dieser Stack?
Supabase Pro startet bei 25 US-Dollar pro Monat inklusive 100.000 MAU; Keycloak selbst ist quelloffen und lizenzkostenfrei, verursacht aber Hosting- und Pflegeaufwand (Stand: Oktober 2026). Die vollständige Rechnung mit Betriebskosten findest du in unserem Stack-Artikel zu Supabase und Keycloak.

Quellen

Ähnliche Artikel

Offen für ausgewählte Projekte

Lassen Sie uns über Ihr Projekt sprechen

Buchen Sie einen unverbindlichen Termin, schreiben Sie uns eine E-Mail oder nutzen Sie das Formular – wir freuen uns auf Ihre Nachricht.

150+
Abgeschlossene Projekte
15
Jahre Erfahrung
8
Senior‑Level Teammitglieder