Zum Inhalt springen

Supabase RLS aktivieren: was jede Anfrage danach liefert

Als Markdown anzeigenIn Claude öffnenIn ChatGPT öffnenIn Perplexity öffnen

Die Frage wird ebenfalls kopiert. Falls der Chat leer öffnet, fügen Sie sie ein.

Kurz gesagt

RLS aktivieren Sie in Supabase mit einer Anweisung pro Tabelle, alter table public.todos enable row level security;, oder mit der Option “Enable RLS” im Dashboard. Tabellen aus dem SQL-Editor oder aus einer Migration haben RLS nicht standardmäßig. Direkt nach dem Aktivieren wird die Tabelle still statt laut. In meinem lokalen Test lieferten der Public Key und zwei angemeldete Nutzer bei select, update und delete 200 [], und nur insert schlug mit 42501 fehl. Der Secret Key sah weiterhin alle 3 Zeilen. Eine einfache View über der Tabelle gab dem Public Key weiterhin alle 3 Zeilen. Seit dem 30. Oktober 2026 braucht eine neue Tabelle außerdem einen Grant, bevor irgendetwas sie erreicht. Aktivieren Sie RLS und schreiben Sie die Policies deshalb in derselben Migration wie den Grant.

Ich betreibe einen kostenlosen Supabase-Sicherheitscheck, und “RLS disabled” ist der Befund, der am schlechtesten erklärt wird. Die meisten Anleitungen zu „Supabase RLS aktivieren“ sagen, nach dem Aktivieren von RLS werde “die Tabelle unzugänglich”. Für Lesezugriffe stimmt das, für Schreibzugriffe führt es in die Irre. Also habe ich eine Tabelle auf einem lokalen Supabase-Stack gebaut und nach jedem der folgenden Schritte jede Art von Anfrage an sie geschickt: als Public Key, als zwei angemeldete Nutzer und als Secret Key.

Was ist RLS in Supabase?

RLS (Row-Level Security) ist ein Postgres-Feature, das Zeilen pro Anfrage filtert. Supabase macht aus jeder Anfrage von supabase-js eine Datenbankabfrage, die als Rolle läuft: anon für den öffentlichen Key (anon, publishable), authenticated für einen angemeldeten Nutzer. Ist RLS an, prüft Postgres Ihre Policies für jede Zeile und gibt nur die Zeilen zurück, ändert oder löscht sie, die eine Policy erlaubt. Ist RLS aus, erreicht jede Rolle mit einem Grant auf die Tabelle jede Zeile.

Der Secret Key (Service Role) ist anders. Seine Rolle hat BYPASSRLS, RLS gilt für ihn also nie. Deshalb gehört er nur auf einen Server. Den Unterschied zwischen den beiden Keys beschreibe ich in Supabase anon key vs service role key: was jeder öffnet.

Wie lässt sich Supabase RLS aktivieren?

Auf drei Wegen, je nachdem, wie die Tabelle entstanden ist:

  • Beim Anlegen einer Tabelle im Table Editor lassen Sie “Enable Row Level Security (RLS)” angehakt. Im Quellcode des Dashboards ist die Option als “Recommended” markiert, und wer sie abschaltet, sieht eine Warnung, dass die Tabelle öffentlich les- und beschreibbar wird.
  • Für eine bestehende Tabelle sagt Supabases API-Sicherheitsleitfaden: Gehen Sie zu Database, dann Policies, und wählen Sie bei der Tabelle “Enable RLS”.
  • In SQL, im SQL-Editor oder in einer Migration:
alter table public.todos enable row level security;

Supabases Leitfaden hält ausdrücklich fest, dass im Dashboard angelegte Tabellen RLS standardmäßig an haben und Tabellen aus dem SQL-Editor oder einem anderen Werkzeug nicht. Wenn Sie Ihr Schema mit Migrationen oder einem KI-Tool bauen, prüfen Sie jede Tabelle.

Was passiert direkt nach dem Aktivieren von RLS?

Nichts schlägt fehl außer insert. Ich habe public.todos mit einer Besitzerspalte (user_id uuid default auth.uid()) angelegt, sie wie in älteren Projekten den API-Rollen gegeben und drei Zeilen eingefügt: zwei gehören Nutzer A, eine Nutzer B. Dann habe ich RLS ohne Policy aktiviert:

Anfrage Public Key Nutzer A Nutzer B Secret Key
Select 200, 0 Zeilen 200, 0 Zeilen 200, 0 Zeilen 200, 3 Zeilen
Insert eigene Zeile 401, Fehler 42501 403, Fehler 42501 403, Fehler 42501 201, Zeile eingefügt
Update einer Zeile 200, 0 Zeilen 200, 0 Zeilen 200, 0 Zeilen 200, 1 Zeile
Delete einer Zeile 200, 0 Zeilen 200, 0 Zeilen 200, 0 Zeilen 200, 1 Zeile

Der Insert-Fehler lautet new row violates row-level security policy. Update und delete antworten mit 200 und einem leeren Array. Eine App, die die zurückgegebenen Zeilen nicht prüft, meldet deshalb “gespeichert”, obwohl nichts gespeichert wurde. Lesen Sie Ihre Schreibzugriffe nach dem Aktivieren von RLS zurück oder fordern Sie mit Prefer: count=exact eine Anzahl an.

Vor RLS konnte der Public Key mit denselben Grants alle 3 Zeilen lesen, einfügen und jede Zeile ändern oder löschen. Nutzer A konnte die Zeile von Nutzer B bearbeiten und löschen und eigene Zeilen an B übergeben.

Wie macht man die Tabelle wieder nutzbar?

Schreiben Sie eine Policy pro Operation. Der übliche Satz für eigene Zeilen:

create policy "own select" on public.todos for select to authenticated
  using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
  with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
  using ((select auth.uid()) = user_id);

Damit sah Nutzer A seine 2 Zeilen und Nutzer B seine 1. Der Public Key sah weiterhin 0. Als A versuchte, eine Zeile mit B als Besitzer einzufügen oder die eigene Zeile an B zu übergeben, lautete die Antwort 403, 42501. Diese Ablehnung kommt von with check beim Update. Als A versuchte, die Zeile von B zu ändern oder zu löschen, lautete die Antwort 200, 0 rows. Weitere Muster (öffentliche Profile, Teams, MFA) stehen in Supabase RLS Policy Beispiele, je per API getestet.

Ein Detail zum Secret Key: Eine Zeile, die er eingefügt hat, bekam user_id = null, weil hinter diesem Key kein Nutzer steht, den auth.uid() liefern könnte. RLS wurde übersprungen, aber der Standardwert tat nicht, was der Spaltenname nahelegt.

Was las der Public Key bei jedem Schritt?

Zeilen, die der Public Key las, von 3. Eine Supabase-Tabelle mit 3 Zeilen, nach jedem Schritt. Neue Tabelle, ohne Grant, RLS aus (42501): 0; Grant ergänzt, RLS aus: 3; RLS an, keine Policy: 0; RLS an, Policies für eigene Zeilen: 0; Einfache View über die Tabelle: 3; View mit security_invoker: 0.
Abbildung 1. Lokaler Supabase-Stack (CLI 2.109.1, Postgres 17.6), 28. Oktober 2026.

Zwei Schritte geben alles preis: ein Grant ohne RLS und eine einfache View über einer Tabelle mit korrekten Policies. Die nächsten beiden Abschnitte erklären beides.

Braucht man nach der Grant-Änderung vom 30. Oktober noch RLS?

Ja. Seit dem 30. Oktober 2026 vergibt Supabase für neue Tabellen in public keine automatischen Grants mehr an die API-Rollen, in neuen und bestehenden Projekten. Mein lokaler Stack verhält sich bereits so. Eine frisch angelegte Tabelle mit RLS aus antwortete jeder Rolle mit 42501, auch dem Secret Key, es floss also nichts ab. Die Einzelheiten stehen in Supabase-Änderung am 30. Oktober: Was bricht, wie prüfen.

Aber der Grant kommt zurück, sobald die App die Tabelle braucht, und von da an ist RLS das Einzige zwischen dem Public Key und jeder Zeile. Legen Sie deshalb alles in eine Migration, in dieser Reihenfolge:

create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;

Vergeben Sie nur an die Rollen, die die Tabelle brauchen. Als ich bei anon alles entzogen habe, wechselte das Select des Public Keys von 200 [] zu 401, 42501. Der Grant ist das erste Schloss, RLS das zweite.

Warum gibt eine View trotzdem Zeilen preis?

Weil eine View mit den Rechten ihres Besitzers läuft, und in Supabase ist das postgres, das RLS umgeht. Mit den Eigene-Zeilen-Policies auf todos lieferte ein einfaches create view public.todos_view as select * from public.todos alle 3 Zeilen an den Public Key und an Nutzer B.

Mit with (security_invoker = true) neu angelegt, lieferte dieselbe View dem Public Key 0 Zeilen, A 2 und B 1, genau wie die Tabelle:

create view public.todos_view with (security_invoker = true) as
  select * from public.todos;

Supabases Advisor meldet die einfache Variante als security_definer_view; was das bedeutet und wie Sie es beheben, steht in Supabase Security-Definer-View: warum sie RLS umgeht.

Wie aktiviert man RLS automatisch für jede neue Tabelle?

Mit einem Event Trigger. Supabases Leitfaden zu Event Triggern liefert eine Funktion rls_auto_enable() und einen Trigger ensure_rls, der nach create table, create table as und select into in public läuft. Ich habe ihn wie veröffentlicht installiert und Folgendes erhalten:

  • Eine danach mit create table angelegte Tabelle hatte RLS an, ohne ein alter. Der Public Key bekam 200 [] bei select und 401 bei insert.
  • Auch eine mit create table as select angelegte Tabelle hatte RLS an.
  • Eine vor dem Trigger angelegte Tabelle blieb aus. Der Trigger wirkt nicht rückwirkend.

Ohne den Trigger hatte eine zweite Tabelle, die neben der geschützten angelegt wurde, RLS aus, und sobald sie einen Grant hatte, las und schrieb der Public Key sie frei. Der Trigger deckt außerdem nur public ab; andere exponierte Schemas brauchen eine eigene Zeile in der Funktion.

Hilft FORCE ROW LEVEL SECURITY?

Nicht bei den Rollen, um die Sie sich sorgen würden. alter table ... force row level security lässt RLS auch für den Tabellenbesitzer gelten. In Supabase haben sowohl postgres als auch service_role BYPASSRLS, also zählte der Besitzer nach force weiter 3 Zeilen, der Secret Key sah weiter alles, und eine View im Besitz von postgres gab weiter Zeilen preis. Etwas änderte sich nur bei einer Besitzerrolle, die ich ohne BYPASSRLS angelegt hatte: vorher 2 Zeilen, nachher 0.

Wie prüft man das eigene Projekt?

  • Listen Sie im SQL-Editor die Tabellen in public auf, bei denen RLS aus ist:
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity
order by 1;

Wie ich getestet habe

  • Supabase CLI 2.109.1 auf einem Laptop: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Zwei Nutzer mit Passwort angemeldet, dazu die lokalen Publishable und Secret Keys.
  • Eine Tabelle, public.todos, vor jeder Anfrage mit denselben 3 Zeilen neu befüllt. Jeder Schritt wurde als SQL angewendet, jede Anfrage lief über die REST-API, Statuscode und Body wurden festgehalten.
  • Die Grants für die Schritte 2 bis 5 habe ich von Hand ergänzt, passend zu einem Projekt, das vor dem 30. Oktober 2026 angelegt wurde.

Grenzen. Ein lokaler Stack, ausgedachte Daten, 28. Oktober 2026. Die Dashboard-Schritte sind aus Supabases Doku zitiert, nicht durchgeklickt. Tabellen, die von der eigenen Rolle des Dashboards angelegt werden, bekommen womöglich andere Standard-Grants. Das habe ich nicht getestet, ebenso wenig Storage, Realtime oder GraphQL.

Unsicher, ob Ihre Tabellen gesperrt sind?

Ich schaue gern gemeinsam mit Ihnen auf ein Schema. Ohne Verkaufsgespräch.

Kontakt aufnehmen

© 2026 Denys Zaichenko. Originalartikel: https://madsalt.tech/de/blog/how-to-enable-rls-in-supabase/