Was RLS ist und warum du es brauchst
Row Level Security ist ein PostgreSQL-Feature, das den Datenbankzugriff auf Zeilenebene steuert. Ohne RLS: Jeder Nutzer mit Datenbankzugriff sieht alle Zeilen. Mit RLS: Du definierst Policies, die bestimmen, welche Zeilen ein Nutzer sehen, erstellen, ändern oder löschen kann — direkt in der Datenbank.
Das ist fundamentaler Unterschied zu anwendungsseitiger Filterung. Wenn du WHERE user_id = current_user_id in deinem Anwendungscode filterst und du einen Bug hast oder jemand die API direkt aufruft, lecken Daten. Mit RLS ist es unmöglich — die Datenbank erzwingt die Regel, egal was der Anwendungscode tut.
Für DSGVO-Compliance in deutschen B2B-Apps ist RLS ein starkes Privacy-by-Design-Argument: Die Datenisolierung ist technisch erzwungen, nicht nur in der Dokumentation beschrieben.
RLS aktivieren und erste Policy schreiben
RLS aktivieren per Tabelle: ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
Ohne Policies wird jetzt jede Abfrage blockiert (kein Zugriff ist der Standard). Erstelle eine SELECT-Policy: CREATE POLICY "users_read_own_projects" ON projects FOR SELECT USING (user_id = auth.uid());
Das erlaubt Nutzern, nur ihre eigenen Zeilen zu lesen. Für INSERT: CREATE POLICY "users_insert_own_projects" ON projects FOR INSERT WITH CHECK (user_id = auth.uid());
Der Unterschied: USING gilt für bestehende Zeilen (SELECT, UPDATE, DELETE). WITH CHECK gilt für neue Zeilen (INSERT, UPDATE). Für UPDATE brauchst du beide:
Multi-Tenant-Policies für B2B-SaaS
Für Multi-Tenancy filterst du auf workspace_id statt user_id. Der Trick: Die workspace_id kommt aus dem JWT-Token des Nutzers.
Erstelle zuerst eine Hilfsfunktion: CREATE OR REPLACE FUNCTION auth.workspace_id() RETURNS uuid AS $$ SELECT (auth.jwt() ->> 'workspace_id')::uuid; $$ LANGUAGE sql STABLE;
Dann deine Policies: CREATE POLICY "workspace_isolation" ON projects FOR ALL USING (workspace_id = auth.workspace_id()) WITH CHECK (workspace_id = auth.workspace_id());
Dieselbe Policy auf allen Tabellen. Das ist das Fundament für DSGVO-konforme Mandantentrennung in deutschen SaaS-Produkten.
Rollenbasierte Policies
Für feingranulare Kontrolle (Admins dürfen mehr als normale Nutzer) erstellst du separate Policies für jede Rolle:
CREATE POLICY "admins_read_all_workspace_data" ON projects FOR SELECT USING ( workspace_id = auth.workspace_id() AND ( auth.jwt() ->> 'role' = 'admin' OR user_id = auth.uid() ) );
Das erlaubt Admins, alle Projekte im Workspace zu sehen, während normale Nutzer nur ihre eigenen sehen. Für Schreiboperationen kannst du ähnliche Policies erstellen, die nur Admins erlauben, fremde Datensätze zu ändern.
Das dreistufige Modell (owner/admin/member), das wir für Mittelstandskunden verwenden, basiert auf diesem Muster.
Performance-Optimierung für RLS
RLS-Policies können Performance-Probleme verursachen, wenn sie nicht korrekt indexiert sind. Jede Policy-Prüfung läuft für jede zurückgegebene Zeile — bei Millionen von Zeilen kann das langsam werden.
Best Practices: Erstelle immer Indizes auf Spalten, die in USING-Klauseln verwendet werden: CREATE INDEX idx_projects_workspace_id ON projects(workspace_id); CREATE INDEX idx_projects_user_id ON projects(user_id);
Verwende STABLE Hilfsfunktionen (wie auth.workspace_id()), die PostgreSQL cachen kann. Vermeide komplexe Subqueries in Policies — erzwinge Joins auf separate Lookup-Tabellen nur wenn nötig.
Mit diesen Optimierungen haben wir RLS-geschützte Tabellen mit 10 Millionen Zeilen in unter 50ms abgefragt.
Häufige Fehler und wie man sie vermeidet
Fehler 1: RLS aktivieren aber keine Policies erstellen. Ergebnis: Alle Abfragen werden blockiert. Immer sofort nach ALTER TABLE ... ENABLE ROW LEVEL SECURITY mindestens eine Basis-Policy erstellen.
Fehler 2: Nur SELECT-Policies schreiben, INSERT/UPDATE/DELETE vergessen. Nutzer können dann keine Daten schreiben. Für jede Operation eine Policy definieren oder eine ALL-Policy verwenden.
Fehler 3: Service Role Key im Frontend-Code verwenden. Der Service Role Key umgeht RLS komplett. Verwende immer den anon-Key im Frontend-Code. Der Service Role Key gehört nur in serverseitige Umgebungen (Xano, Edge Functions).
Fehler 4: Vergessen, Joins in RLS-Policies zu indexieren. Führe EXPLAIN ANALYZE auf deinen häufigsten Queries aus und stelle sicher, dass alle Columns in USING-Klauseln indexiert sind. App Studio führt das bei jedem Produktions-Deployment durch.