TL;DR
Aktivér RLS i Supabase med én sætning pr. tabel, alter table public.todos enable row level security;, eller med valget “Enable RLS” i dashboardet. Tabeller oprettet i SQL-editoren eller af en migration får det ikke som standard. Lige efter du har aktiveret det, bliver tabellen stille frem for højlydt. På min lokale test fik den offentlige nøgle og to loggede brugere 200 [] på select, update og delete, og kun insert fejlede med 42501. Den hemmelige nøgle så stadig alle 3 rækker. En almindelig view over tabellen gav stadig alle 3 rækker til den offentlige nøgle. Siden den 30. oktober 2026 kræver en ny tabel også et grant, før noget når den, så aktivér RLS og skriv policies i samme migration som granten.
Jeg driver en gratis Supabase security checker, og “RLS disabled” er det fund, jeg ser forklaret dårligst. De fleste guides siger, at efter du har aktiveret RLS, “bliver tabellen utilgængelig”. Det er sandt for læsning og vildledende for skrivning. Så jeg byggede én tabel på en lokal Supabase-stack og sendte alle slags forespørgsler til den, som den offentlige nøgle, som to loggede brugere og som den hemmelige nøgle, efter hvert trin nedenfor.
Hvad er RLS i Supabase?
RLS (row-level security) er en Postgres-funktion, der filtrerer rækker pr. forespørgsel. Supabase gør hver forespørgsel fra supabase-js til en databaseforespørgsel, der kører som en rolle: anon for den offentlige (anon, publishable) nøgle, authenticated for en logget bruger. Med RLS slået til tjekker Postgres dine policies for hver række og returnerer, ændrer eller sletter kun de rækker, en policy tillader. Med RLS slået fra når enhver rolle med et grant på tabellen alle rækker.
Den hemmelige nøgle (service role) er anderledes. Dens rolle har BYPASSRLS, så RLS gælder aldrig for den. Derfor hører den kun hjemme på en server. Forskellen på de to nøgler står i Supabase anon key vs service role key: hvad åbner de?.
Hvordan aktiverer du RLS i Supabase?
Tre måder, afhængigt af hvordan tabellen blev lavet:
- Når du opretter en tabel i Table Editor, så lad “Enable Row Level Security (RLS)” være afkrydset. I Supabases dashboard-kode er det markeret “Recommended”, og slår du det fra, vises en advarsel om, at tabellen bliver offentligt læsbar og skrivbar.
- For en eksisterende tabel siger Supabases guide til sikring af API’et: gå til Database, derefter Policies, og vælg “Enable RLS” på tabellen.
- I SQL, i SQL-editoren eller en migration:
alter table public.todos enable row level security;
Supabases guide er tydelig på, at tabeller oprettet i dashboardet har RLS slået til som standard, og at tabeller oprettet i SQL-editoren eller med et andet værktøj ikke har det. Bygger du dit skema med migrationer eller et AI-værktøj, så tjek hver tabel.
Hvad sker der lige efter, du aktiverer RLS?
Intet giver fejl undtagen insert. Jeg oprettede public.todos med en ejerkolonne (user_id uuid default auth.uid()), gav den grants til API-rollerne på samme måde som ældre projekter og tilføjede tre rækker: to ejet af bruger A og én af bruger B. Derefter aktiverede jeg RLS uden policy:
| Forespørgsel | Offentlig nøgle | Bruger A | Bruger B | Hemmelig nøgle |
|---|---|---|---|---|
| Select | 200, 0 rækker | 200, 0 rækker | 200, 0 rækker | 200, 3 rækker |
| Insert egen række | 401, fejl 42501 | 403, fejl 42501 | 403, fejl 42501 | 201, række indsat |
| Update en række | 200, 0 rækker | 200, 0 rækker | 200, 0 rækker | 200, 1 række |
| Delete en række | 200, 0 rækker | 200, 0 rækker | 200, 0 rækker | 200, 1 række |
Insert-fejlen lyder new row violates row-level security policy. Update og delete svarer 200 med en tom liste, så en app, der ikke tjekker de returnerede rækker, melder “gemt”, når intet blev gemt. Læs dine skrivninger tilbage, efter du har aktiveret RLS, eller bed om et antal med Prefer: count=exact.
Før RLS, med de samme grants, kunne den offentlige nøgle læse alle 3 rækker, indsætte og opdatere eller slette enhver række. Bruger A kunne redigere og slette bruger Bs række og give sine egne rækker til B.
Hvordan gør du tabellen brugbar igen?
Skriv en policy pr. operation. Det sædvanlige sæt for egne rækker:
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);
Med dem så bruger A sine 2 rækker og bruger B sin 1. Den offentlige nøgle så stadig 0. Da A forsøgte at indsætte en række ejet af B eller at flytte sin egen række til B, var svaret 403, 42501. Den afvisning kommer fra with check på update. Da A forsøgte at opdatere eller slette Bs række, var svaret 200, 0 rækker. Flere mønstre (offentlige profiler, teams, MFA) står i Supabase RLS policy-eksempler, hver testet via API.
En detalje fra den hemmelige nøgle: en række, den indsatte, fik user_id = null, fordi auth.uid() ikke har nogen bruger bag den nøgle. RLS blev sprunget over, men standardværdien gjorde ikke det, kolonnenavnet antyder.
Hvad læste den offentlige nøgle efter hvert trin?
To trin lækker alt: et grant uden RLS og en almindelig view over en tabel med korrekte policies. De næste to afsnit forklarer begge.
Har du stadig brug for RLS efter grant-ændringen den 30. oktober?
Ja. Siden den 30. oktober 2026 giver Supabase ikke længere nye tabeller i public automatiske grants til API-rollerne, hverken på nye eller eksisterende projekter. Min lokale stack opfører sig allerede sådan. En nyoprettet tabel med RLS slået fra svarede 42501 til hver rolle, også den hemmelige nøgle, så intet lækkede. Detaljerne står i Supabase 30. oktober: hvad knækker, og hvordan tjekker du.
Men granten kommer tilbage, så snart appen skal bruge tabellen, og fra da af er RLS det eneste mellem den offentlige nøgle og hver række. Så læg det hele i én migration, i denne rækkefølge:
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;
Giv kun grants til de roller, der har brug for tabellen. Da jeg tog alt fra anon, skiftede den offentlige nøgles select fra 200 [] til 401, 42501. Granten er den første lås, RLS er den anden.
Hvorfor lækker en view stadig rækker?
Fordi en view kører med sin ejers rettigheder, og i Supabase er det postgres, som omgår RLS. Med policies for egne rækker på plads på todos gav en almindelig create view public.todos_view as select * from public.todos alle 3 rækker til den offentlige nøgle og til bruger B.
Oprettet igen with (security_invoker = true) gav den samme view 0 rækker til den offentlige nøgle, 2 til A og 1 til B, præcis som tabellen:
create view public.todos_view with (security_invoker = true) as
select * from public.todos;
Supabases Advisor markerer den almindelige slags som security_definer_view; hvad det betyder, og hvordan du retter det, står i noten om Supabase security definer view.
Hvordan aktiverer du RLS automatisk på hver ny tabel?
Med en event trigger. Supabases guide til event triggers giver en funktion rls_auto_enable() og en trigger ensure_rls, der kører efter create table, create table as og select into i public. Jeg installerede den som udgivet og fik:
- En tabel oprettet bagefter med
create tablehavde RLS slået til uden nogenalter. Den offentlige nøgle fik200 []på select og401på insert. - En tabel oprettet med
create table as selecthavde også RLS slået til. - En tabel oprettet før triggeren forblev slået fra. Triggeren virker ikke bagudrettet.
Uden triggeren havde en anden tabel, oprettet ved siden af den beskyttede, RLS slået fra, og når den fik et grant, kunne den offentlige nøgle læse og skrive den frit. Den dækker heller kun public; andre eksponerede skemaer skal have deres egen linje i funktionen.
Hjælper FORCE ROW LEVEL SECURITY?
Ikke for de roller, du bekymrer dig om. alter table ... force row level security får RLS til også at gælde for tabellens ejer. På Supabase har både postgres og service_role BYPASSRLS, så efter force talte ejeren stadig 3 rækker, den hemmelige nøgle så stadig alt, og en view ejet af postgres lækkede stadig. Det ændrede kun noget for en ejerrolle, jeg oprettede uden BYPASSRLS: 2 rækker før, 0 efter.
Hvordan tjekker du dit eget projekt?
- Vis i SQL-editoren tabellerne i
publicmed RLS slået fra:
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;
- Advisor i dashboardet rapporterer det samme som
rls_disabled_in_public; hvad den fejl betyder, står i noten om RLS disabled in public. - Det gratis Supabase security check læser dit projekt skrivebeskyttet og viser tabeller med RLS slået fra, som API’et kan nå, views der omgår RLS, og policies der tillader alle, med rette-SQL til hver.
- En scanner kan ikke vide, hvilke rækker der tilhører hvem. For at bevise det skal du sende forespørgsler som to brugere, som ovenfor. Den fulde metode for en app med roller står i Test Supabase RLS: 30 af 87 rettigheder gjorde intet.
Sådan testede jeg
- Supabase CLI 2.109.1 på en bærbar: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. To brugere logget ind med adgangskode, plus de lokale publishable og secret keys.
- Én tabel,
public.todos, nulstillet til de samme 3 rækker før hver forespørgsel. Hvert trin blev udført som SQL, og hver forespørgsel gik over REST-API’et, med statuskode og body noteret. - Grants til trin 2 til 5 blev tilføjet i hånden, så de svarer til et projekt oprettet før den 30. oktober 2026.
Begrænsninger. Én lokal stack, opfundne data, 28. oktober 2026. Dashboard-trinnene er citeret fra Supabases dokumentation, ikke klikket igennem. Tabeller oprettet af dashboardets egen rolle kan få andre standardgrants. Det testede jeg ikke, og heller ikke Storage, Realtime eller GraphQL.
Usikker på, om dine tabeller er låst?
Jeg ser gerne på et skema sammen med dig. Uden salgstale.
Kontakt mig