Liigu sisu juurde

Supabase RLS policy näited, testitud API kaudu

Vaata MarkdowninaAva rakenduses ClaudeAva rakenduses ChatGPTAva rakenduses Perplexity

Küsimus kopeeritakse ka. Kui vestlus avaneb tühjana, kleepige see sisse.

Lühidalt

Allpool on RLS-poliitikad, mida enamik Supabase’i rakendusi vajab, ja iga ühte on käivitatud REST API kaudu külalisena, rea omanikuna ja teise sisselogitud kasutajana. Kolme tulemust tasub teada enne, kui midagi kopeerite: võõra rea update annab tagasi 200 ja tühja nimekirja, mitte vea; kahjutu välimusega insert-poliitika liikmetabelil lasi kasutajal liituda teise ettevõttega ja lugeda selle projekte; ja kui kirjutasin auth.uid() asemel (select auth.uid()), kiirenes üks päring 200 000 real 156 ms-st 7,4 ms-ni.

Olen Denys, full-stack- ja tehisintellekti insener, ning enamik rakendusi, mida ehitan või parandan, töötab Supabase’is. Poliitikate koodilõike on igal pool; mida nad teevad, kui neid tabab päris päring, näidatakse harva. Nii et käivitasin need. Need on Supabase RLS policy näited, millest ise alustan, koos igaühe tulemusega.

Kuidas testisin. Enamik päringuid käis läbi kohaliku Supabase’i stacki (CLI 2.109.1, Postgres 17.6, PostgREST 14.14, Auth 2.192.0) avaliku võtme ja kahe päris sisselogitud kasutajaga, A ja B. Juhud, mis HTTP-d ei vaja, näiteks süntaksivead ja ajamõõtmised, jooksid tavalisel kohalikul Postgres 17.10-l, kus Supabase’i rollid anon ja authenticated ning auth.uid() olid uuesti loodud. Kõik 24. oktoobril 2026, väljamõeldud ridadega.

Enne ühtegi poliitikat: kas tabelil on grant?

Poliitika otsustab, milliseid ridu roll näeb. Grant otsustab, kas roll jõuab tabelini üldse, ja praegustes Supabase’i projektides uued tabelid Data API grante enam automaatselt ei saa (30. oktoobrist 2026 igas projektis). Minu stackis vastas tabel, millel oli RLS sees, aga grant puudus, nii:

Päring Olek Sisu
Külaline GET /invoices 401 permission denied for table invoices, vihjega GRANT SELECT ... TO anon
Kasutaja A GET /invoices 403 sama, vihjega rollile authenticated

Seega iga tabeli puhul, mida rakendus brauserist loeb, on esimene samm:

alter table public.todos enable row level security;
grant select, insert, update, delete on public.todos to authenticated;

Andke ainult need käsud, mida brauser vajab, ja ainult rollile authenticated, kui külalised ei pea tabelit lugema.

Kuidas lasta kasutajatel näha ainult oma ridu?

Üks poliitika käsu kohta, igaüks piiratud sisselogitud kasutajatega:

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

A-l on kaks rida, B-l üks. Mis tagasi tuli:

Päringu tegija Mida Olek Sisu
Külaline loe kõik 401 permission denied (rollil anon pole granti)
A loe kõik 200 A kaks rida
A insert B-le kuuluv rida 403 new row violates row-level security policy
A update B rida 200 []
A delete B rida 200 []
A muuda oma rea omanikuks B 403 new row violates row-level security policy

Need kaks 200 [] rida tasub meelde jätta. RLS ei lükka tagasi update’i ega delete’i ridadele, mida te ei näe; ta filtreerib need välja ja null muudetud rida on edu. Kui teie rakendus peab “viga puudub” märgiks, et salvestamine õnnestus, ütleb see kasutajale, et muudatus töötas, kuigi midagi ei juhtunud. Küsige muudetud read tagasi (.select() supabase-js-is, Prefer: return=representation HTTP-ga) ja kontrollige, et üks tuli.

Miks vajab update-poliitika WITH CHECK-i?

using valib, milliseid olemasolevaid ridu kasutaja puudutada tohib; with check otsustab, milline need read pärast välja näevad. Võrdlesin sama update-poliitika kolme versiooni:

Update-poliitika A annab oma rea B-le
using (uid = user_id) with check (uid = user_id) 403, keelatud
using (uid = user_id), ilma with check-ita keelatud (Postgres kasutab using-i ka checkina)
using (uid = user_id) with check (true) 200, rida kuulub nüüd B-le

with check-i väljajätmine on turvalisem, kui paistab, sest Postgres kasutab tagavarana using-avaldist. Kasutaja saab rea teise konto alla viia with check (true) abil, mille mõned generaatorid kirjutavad “update’ide lubamiseks”. Kirjutage check täielikult välja.

Kuidas teha profiilid avalikuks, aga muudetavaks ainult omaniku poolt?

create policy "public read" on public.profiles
  for select to anon, authenticated using (true);
create policy "own edit" on public.profiles
  for update to authenticated
  using ((select auth.uid()) = id) with check ((select auth.uid()) = id);

Tavalise Postgresi käivitusel: külaline luges mõlemad profiilid, külalise update muutis 0 rida ja A update profiles set display_name = 'x' ilma filtrita muutis täpselt 1 rea, A enda oma. using (true) sobib selectile andmetel, mis peavadki avalikud olema. Igal teisel tabelil on see sama mis RLS-i puudumine, ja ilma to-klauslita kirjutatud poliitika kehtib ka külalistele: minu külaline luges “privaatse” rea poliitikaga using (true), millel polnud to-d.

Kuidas jagada ridu tiimi sees?

Tavaline kuju: tabel memberships ja andmete poliitika, mis otsib sealt kasutaja tiimid.

create policy "members read" on public.projects
  for select to authenticated
  using (org_id in (select org_id from public.memberships
                    where user_id = (select auth.uid())));

See poliitika on korras. Leke oli selle kõrval. Andsin tabelile memberships poliitika, mida paljud õpetused soovitavad, “kasutajad võivad lisada oma liikmesuse”:

create policy "join any org" on public.memberships
  for insert to authenticated with check (user_id = (select auth.uid()));

See kontrollib, kellele rida kuulub, mitte millisesse tiimi. A, ettevõtte 1 liige, saatis ühe päringu:

Üks insert-poliitika tabelil memberships. Ettevõtte 1 kasutaja A, REST API kaudu. Poliitika: user_id = auth.uid(): Kasutaja A → liitub ettevõttega 2 → 201 Created → loeb B palka; Insert-poliitikat pole: Kasutaja A → liitub ettevõttega 2 → keelatud → ainult oma projektid.
Joonis 1. Minu test kohalikus Supabase'i stackis (CLI 2.109.1), 24. oktoobril 2026.
A päring Olek Sisu
loe projektid 200 A roadmap
insert liikmesus {user_id: A, org_id: 2} 201 loodud
loe projektid uuesti 200 A roadmap, B payroll

Lahendus on mitte anda tabelile memberships üldse insert-poliitikat, et brauser ei saaks sinna kirjutada (tavalisel Postgresil lükati sama insert siis tagasi veaga 42501), ja lisada liikmed kutsevoo kaudu, mis jookseb serveris või funktsioonis, mis kutset kontrollib. Kasutaja, kes kuulub kahte ettevõttesse, on järgmine lõks; kirjutasin sellest artiklis Supabase RLS: kuidas kahe org-i kasutaja andmeid lekitab.

Mis siis, kui poliitika peab lugema oma tabelit?

“Liikmed näevad oma meeskonnakaaslasi” tähendab poliitikat tabelil memberships, mis loeb tabelit memberships. Postgres keeldub päringu ajal:

ERROR 42P17: infinite recursion detected in policy for relation "members2"

Tõstke otsing funktsiooni, mis jookseb omaniku õigustes, skeemas, mida API ei avalda, fikseeritud search_path-iga:

create schema private;
create function private.user_org_ids() returns setof int
  language sql stable security definer set search_path = ''
  as $$ select org_id from public.members2 where user_id = (select auth.uid()) $$;
revoke all on function private.user_org_ids() from public;
grant usage on schema private to authenticated;
grant execute on function private.user_org_ids() to authenticated;

create policy "see teammates" on public.members2
  for select to authenticated using (org_id in (select private.user_org_ids()));

Pärast seda nägi A ettevõtte 1 mõlemat liiget ja B nägi ainult iseennast. Hoidke funktsioon skeemast public väljas: kõik, mis seal on, on API kaudu kutsutav.

Kuidas nõuda tundlike ridade jaoks MFA-d?

Lisage tavalise poliitika peale piirav poliitika. Piiravad poliitikad ühendatakse and-iga, seega mõlemad peavad läbi minema:

create policy "mfa only" on public.payouts as restrictive
  for select to authenticated using ((select auth.jwt() ->> 'aal') = 'aal2');

Sama kasutajaga luges aal1 seanss 0 väljamakset ja aal2 seanss 1.

Millistel tabelitel peaks RLS olema sees ja poliitikat mitte?

Tabelitel, mida puudutab ainult teie server. RLS sees ilma poliitikata keelab brauseri rollidele kõik: tavalisel Postgresil lugesid külaline ja A 0 rida ning A insert lükati tagasi veaga 42501. RLS-i väljalülitatuks jätmine on vastupidine. Tabel, millel on RLS väljas ja vaikimisi grandid, loetuna avaliku võtmega ilma sisselogimiseta:

Külalise päring Olek Sisu
GET /leads 200 mõlemad read
DELETE /leads?id=gt.0 200 mõlemad read, kustutatud

Just sellest räägib Advisori hoiatus “RLS disabled in public”.

Kas üks poliitika võib katta mitut käsku?

Mitte nimekirjaga. for update, delete on süntaksiviga (42601: syntax error at or near ","), kuigi see esineb endiselt avaldatud koodilõikude kogudes. for all on kehtiv ja rakendab ühte using- ja ühte with check-avaldist kõigile käskudele, mida harva soovitakse. Ka insert-poliitika koos using-iga ebaõnnestub: only WITH CHECK expression allowed for INSERT.

Kas poliitikad aeglustavad päringuid?

Võivad küll. Poliitika avaldis jookseb iga rea kohta, mida Postgres kaalub. Mõõtsin select * from big kasutajana A 200 000 real, mille omanikud on 1 000 kasutajat, neist A-le kuulub 200 (7 käivituse mediaan, Postgres 17.10):

Üks omanikupoliitika, neli kirjutusviisi. Millisekundeid 200 oma rea lugemiseks 200 000-st. 7 käivituse mediaan. auth.uid() = user_id, indeksita: 156,30; (select auth.uid()), indeksita: 7,40; auth.uid() = user_id, indeksiga: 0,21; (select auth.uid()), indeksiga: 0,05.
Joonis 2. Minu test kohalikul Postgres 17.10-l, kus Supabase'i rollid olid uuesti loodud, 24. oktoobril 2026.
Poliitika ja indeks Mediaan
auth.uid() = user_id, indeksita 156,3 ms
(select auth.uid()) = user_id, indeksita 7,4 ms
auth.uid() = user_id, indeks veerul user_id 0,21 ms
(select auth.uid()) = user_id, indeksiga 0,05 ms

Kutse mähkimine select-i sisse lubab Postgresil see arvutada üks kord päringu kohta, mitte üks kord rea kohta; ülejäänu teeb indeks. Supabase’i RLS-i jõudluse juhend toob oma 100 000 realises tabelis sama suurusjärgu muutuse (179 ms-st 9 ms-ni). Paljud blogipostitused tsiteerivad neid numbreid enda omadena; ülaltoodud on minu käivitusest.

Kust peaksid rollid tulema?

Väljalt app_metadata või teie enda tabelist, mitte kunagi väljalt user_metadata. Sisselogitud kasutaja saab oma user_metadata-t muuta käsuga supabase.auth.updateUser(), nii et poliitika, mis kontrollib auth.jwt() -> 'user_metadata' ->> 'role', lubab tal rolli endale anda. Registreerimistrigger, mis kopeerib rolli registreerimise metaandmetest, teeb sama; see juhtum on artiklis Supabase raw_user_meta_data: registreerimine ja Owner-roll.

Kuidas kontrollida oma poliitikaid?

Piirangud. Üks kohalik stack ja üks sülearvuti, väljamõeldud andmed, 24. oktoober 2026. Ajad sõltuvad riistvarast ja tabeli kujust; suhted loevad rohkem kui millisekundid. Storage’i poliitikaid ja Realtime’i siin ei käsitleta.

Pole kindel, kas teie poliitikad peavad vastu?

Vaatan hea meelega koos teiega skeemi. Müügijututa.

Võtke ühendust

© 2026 Denys Zaichenko. Algne artikkel: https://madsalt.tech/et/blog/supabase-rls-policy-examples/