---
title: "Enable RLS in Supabase: mitä taulu palauttaa sen jälkeen"
description: "Enable RLS in Supabase paneelista tai SQL:llä, testattuna paikallisella stackilla: mitä julkinen avain, kaksi käyttäjää ja salainen avain saavat."
url: "https://madsalt.tech/fi/blog/how-to-enable-rls-in-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/fi/about/"
language: "fi"
published: 2026-11-04
---

# Enable RLS in Supabase: mitä taulu palauttaa sen jälkeen

TL;DR

RLS otetaan käyttöön Supabasessa yhdellä lauseella taulua kohden, `alter table public.todos enable row level security;`, tai hallintapaneelin valinnalla "Enable RLS". SQL-editorissa tai migraatiolla luodut taulut eivät saa sitä oletuksena. Heti käyttöönoton jälkeen taulu hiljenee eikä huuda. Paikallisessa testissäni julkinen avain ja kaksi kirjautunutta käyttäjää saivat `200 []` selectiin, updateen ja deleteen, ja vain insert epäonnistui virheellä `42501`. Salainen avain näki yhä kaikki 3 riviä. Pelkkä näkymä (view) taulun päällä antoi yhä kaikki 3 riviä julkiselle avaimelle. 30. lokakuuta 2026 alkaen uusi taulu tarvitsee myös granttin ennen kuin mikään pääsee siihen, joten ota RLS käyttöön ja kirjoita politiikat samassa migraatiossa kuin grant.

Ylläpidän ilmaista [Supabasen tietoturvatarkistusta](https://madsalt.tech/fi/tools/supabase-security-check/), ja "RLS disabled" on löytö, jonka selitetään huonoimmin. Useimmat oppaat sanovat, että RLS:n käyttöönoton jälkeen "taulu muuttuu saavuttamattomaksi". Se pitää paikkansa lukemisen osalta ja johtaa harhaan kirjoittamisen osalta. Siksi tein yhden taulun paikalliseen Supabase-stackiin ja lähetin siihen kaikenlaisia pyyntöjä: julkisella avaimella, kahtena kirjautuneena käyttäjänä ja salaisella avaimella, jokaisen alla olevan vaiheen jälkeen.

## Mikä RLS on Supabasessa?

RLS (row-level security) on Postgresin ominaisuus, joka suodattaa rivejä pyyntökohtaisesti. Supabase muuttaa jokaisen `supabase-js`-pyynnön tietokantakyselyksi, joka ajetaan roolina: `anon` julkiselle (anon, publishable) avaimelle ja `authenticated` kirjautuneelle käyttäjälle. RLS päällä Postgres tarkistaa politiikkasi jokaiselle riville ja palauttaa, muuttaa tai poistaa vain ne rivit, jotka jokin politiikka sallii. RLS pois päältä mikä tahansa rooli, jolla on taulussa grant, pääsee kaikkiin riveihin.

Salainen (service role) avain on erilainen. Sen roolilla on `BYPASSRLS`, joten RLS ei koskaan koske siihen. Siksi se kuuluu vain palvelimelle. Ero näiden kahden avaimen välillä on artikkelissa [Supabase anon key vs service role key: mitä kumpikin avaa](https://madsalt.tech/fi/blog/supabase-anon-key-vs-service-role-key/).

## Miten enable RLS in Supabase onnistuu?

Kolmella tavalla sen mukaan, miten taulu tehtiin:

- **Kun luot taulun Table Editorissa**, jätä "Enable Row Level Security (RLS)" valituksi. Supabasen [hallintapaneelin lähdekoodissa](https://github.com/supabase/supabase/commit/33bd798d142183e162c25debcc50c8c711e68f66) se on merkitty "Recommended", ja sen poistaminen näyttää varoituksen, että taulu on julkisesti luettavissa ja kirjoitettavissa.
- **Olemassa olevalle taululle** Supabasen [API-turvaopas](https://supabase.com/docs/guides/api/securing-your-api) neuvoo: mene kohtaan Database, sitten Policies, ja valitse taulusta "Enable RLS".
- **SQL:llä** SQL-editorissa tai migraatiossa:

```sql
alter table public.todos enable row level security;
```

Supabasen opas sanoo selvästi, että hallintapaneelissa luoduissa tauluissa RLS on oletuksena päällä, mutta SQL-editorissa tai muulla työkalulla luoduissa ei ole. Jos rakennat skeeman migraatioilla tai tekoälytyökalulla, tarkista jokainen taulu.

## Mitä tapahtuu heti RLS:n käyttöönoton jälkeen?

Mikään ei anna virhettä paitsi insert. Loin taulun `public.todos` omistajasarakkeella (`user_id uuid default auth.uid()`), annoin siihen granttit API-rooleille niin kuin vanhemmissa projekteissa, ja lisäsin kolme riviä: kaksi käyttäjän A ja yhden käyttäjän B. Sitten otin RLS:n käyttöön ilman politiikkaa:

| Pyyntö                | Julkinen avain   | Käyttäjä A       | Käyttäjä B       | Salainen avain    |
| --------------------- | ---------------- | ---------------- | ---------------- | ----------------- |
| Select                | 200, 0 riviä     | 200, 0 riviä     | 200, 0 riviä     | 200, 3 riviä      |
| Insert omalla rivillä | 401, virhe 42501 | 403, virhe 42501 | 403, virhe 42501 | 201, rivi lisätty |
| Update riville        | 200, 0 riviä     | 200, 0 riviä     | 200, 0 riviä     | 200, 1 rivi       |
| Delete riville        | 200, 0 riviä     | 200, 0 riviä     | 200, 0 riviä     | 200, 1 rivi       |

Insert-virhe kuuluu `new row violates row-level security policy`. Update ja delete vastaavat `200` ja tyhjällä listalla, joten sovellus, joka ei tarkista palautettuja rivejä, ilmoittaa "tallennettu", vaikka mitään ei tallennettu. Lue kirjoituksesi takaisin RLS:n käyttöönoton jälkeen tai pyydä lukumäärä otsakkeella `Prefer: count=exact`.

Ennen RLS:ää samoilla granteilla julkinen avain pystyi lukemaan kaikki 3 riviä, lisäämään rivejä sekä päivittämään tai poistamaan mitä tahansa riviä. Käyttäjä A pystyi muokkaamaan ja poistamaan käyttäjän B rivin ja antamaan omat rivinsä B:lle.

## Miten taulu saadaan taas käyttökelpoiseksi?

Kirjoita politiikka jokaiselle komennolle. Tavallinen omien rivien sarja:

```sql
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);
```

Näillä käyttäjä A näki 2 riviään ja käyttäjä B 1. Julkinen avain näki yhä 0. Kun A yritti lisätä rivin, jonka omistaja on B, tai siirtää oman rivinsä B:lle, vastaus oli `403, 42501`. Kieltäytyminen tulee updaten `with check` -ehdosta. Kun A yritti päivittää tai poistaa B:n rivin, vastaus oli `200, 0 riviä`. Lisää malleja (julkiset profiilit, tiimit, MFA) on artikkelissa [Supabase RLS policy -esimerkit testattuna API:n kautta](https://madsalt.tech/fi/blog/supabase-rls-policy-examples/).

Yksi havainto salaisesta avaimesta: sen lisäämä rivi sai `user_id = null`, koska `auth.uid()`:lla ei ole käyttäjää tämän avaimen takana. RLS ohitettiin, mutta oletusarvo ei tehnyt sitä, mitä sarakkeen nimi antaa ymmärtää.

## Mitä julkinen avain luki kunkin vaiheen jälkeen?

![Rivit, jotka julkinen avain luki, 3:sta. Yksi Supabase-taulu, 3 riviä, jokaisen vaiheen jälkeen. Uusi taulu, ei grantia, RLS pois (virhe 42501): 0; Grant lisätty, RLS pois: 3; RLS päällä, ei politiikkaa: 0; RLS päällä, omien rivien politiikat: 0; Pelkkä näkymä taulun päällä: 3; Näkymä, security_invoker: 0.](https://madsalt.tech/charts/fi/how-to-enable-rls-in-supabase.svg)

*Kuva 1. Paikallinen Supabase-stack (CLI 2.109.1, Postgres 17.6), 28. lokakuuta 2026.*

Kaksi vaihetta vuotaa kaiken: grant ilman RLS:ää ja pelkkä näkymä taulun päällä, jonka politiikat ovat kunnossa. Seuraavat kaksi osiota selittävät molemmat.

## Tarvitaanko RLS:ää yhä 30. lokakuun grant-muutoksen jälkeen?

Kyllä. 30. lokakuuta 2026 alkaen Supabase ei enää anna uusille tauluille skeemassa `public` automaattisia granteja API-rooleille, uusissa ja olemassa olevissa projekteissa. Paikallinen stackini toimii jo niin. Juuri luotu taulu, jolla RLS oli pois päältä, vastasi `42501` kaikille rooleille, salainen avain mukaan lukien, joten mikään ei vuotanut. Yksityiskohdat ovat artikkelissa [Supabasen grant-muutos 30. lokakuuta: mikä hajoaa](https://madsalt.tech/fi/blog/supabase-october-30-grant-change/).

Mutta grant palaa heti, kun sovellus tarvitsee taulun, ja siitä lähtien RLS on ainoa asia julkisen avaimen ja jokaisen rivin välissä. Laita siis kaikki yhteen migraatioon tässä järjestyksessä:

```sql
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;
```

Anna grant vain rooleille, jotka taulua tarvitsevat. Kun peruutin kaiken roolilta `anon`, julkisen avaimen select muuttui vastauksesta `200 []` vastaukseksi `401, 42501`. Grant on ensimmäinen lukko, RLS toinen.

## Miksi näkymä silti vuotaa rivejä?

Koska näkymä ajetaan omistajansa oikeuksilla, ja Supabasessa se on `postgres`, joka ohittaa RLS:n. Kun `todos`-taulussa oli omien rivien politiikat, pelkkä `create view public.todos_view as select * from public.todos` palautti kaikki 3 riviä julkiselle avaimelle ja käyttäjälle B.

Kun sama näkymä luotiin uudelleen `with (security_invoker = true)`, se palautti julkiselle avaimelle 0 riviä, A:lle 2 ja B:lle 1, täsmälleen kuten taulu:

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

Supabasen Advisor liputtaa pelkän näkymän tunnuksella `security_definer_view`; mitä se tarkoittaa ja miten sen korjaa, on muistiinpanossa [Supabase security definer view: miksi se ohittaa RLS:n](https://madsalt.tech/fi/notes/supabase-security-definer-view/).

## Miten RLS otetaan käyttöön automaattisesti jokaiselle uudelle taululle?

Tapahtumatriggerillä. Supabasen [event triggers -opas](https://supabase.com/docs/guides/database/postgres/event-triggers) antaa funktion `rls_auto_enable()` ja triggerin `ensure_rls`, joka ajetaan `create table`-, `create table as`- ja `select into` -lauseiden jälkeen skeemassa `public`. Asensin sen sellaisenaan julkaistuna ja sain:

- Myöhemmin lauseella `create table` luodussa taulussa RLS oli päällä ilman `alter`-lausetta. Julkinen avain sai `200 []` selectiin ja `401` insertiin.
- Lauseella `create table as select` luodussa taulussa RLS oli myös päällä.
- Ennen triggeriä luotu taulu pysyi pois päältä. Trigger ei toimi taaksepäin.

Ilman triggeriä suojatun taulun viereen luodussa toisessa taulussa RLS oli pois päältä, ja kun siihen annettiin grant, julkinen avain luki ja kirjoitti sitä vapaasti. Trigger kattaa myös vain skeeman `public`; muut julkaistut skeemat tarvitsevat oman rivinsä funktioon.

## Auttaako FORCE ROW LEVEL SECURITY?

Ei niille rooleille, joista kannattaa olla huolissaan. `alter table ... force row level security` panee RLS:n koskemaan myös taulun omistajaa. Supabasessa sekä `postgres` että `service_role` ovat `BYPASSRLS`, joten `force`n jälkeen omistaja laski yhä 3 riviä, salainen avain näki yhä kaiken ja `postgres`in omistama näkymä vuoti yhä. Se muutti jotain vain omistajaroolille, jonka loin ilman `BYPASSRLS`: 2 riviä ennen, 0 jälkeen.

## Miten tarkistat oman projektisi?

- Listaa SQL-editorissa skeeman `public` taulut, joilla RLS on pois päältä:

```sql
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;
```

- Hallintapaneelin Advisor raportoi saman tunnuksella `rls_disabled_in_public`; mitä tämä virhe tarkoittaa, on muistiinpanossa [Supabase "RLS disabled in public": mitä se tarkoittaa](https://madsalt.tech/fi/notes/supabase-rls-disabled-in-public/).
- Ilmainen [Supabasen tietoturvatarkistus](https://madsalt.tech/fi/tools/supabase-security-check/) lukee projektisi vain luku -tilassa ja listaa taulut, joilla RLS on pois päältä ja joihin API yltää, RLS:n ohittavat näkymät ja politiikat, jotka sallivat kaikille, kullekin korjaus-SQL:llä.
- Skanneri ei voi tietää, mitkä rivit kuuluvat kenellekin. Sen todistamiseksi lähetä pyyntöjä kahtena käyttäjänä, kuten yllä. Koko menetelmä rooleja käyttävälle sovellukselle on artikkelissa [Supabase RLS:n testaus: 30 oikeutta 87:stä ei tehnyt mitään](https://madsalt.tech/fi/blog/how-to-test-supabase-rls/).

## Miten testasin

- Supabase CLI 2.109.1 kannettavalla: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Kaksi käyttäjää kirjautui salasanalla, lisäksi paikalliset publishable- ja secret-avaimet.
- Yksi taulu, `public.todos`, alustettu samoilla 3 rivillä ennen jokaista pyyntöä. Jokainen vaihe ajettiin SQL:nä ja jokainen pyyntö kulki REST API:n kautta, tilakoodi ja runko tallennettuna.
- Vaiheiden 2–5 granttit lisättiin käsin vastaamaan projektia, joka on luotu ennen 30. lokakuuta 2026.

**Rajoitukset.** Yksi paikallinen stack, keksittyä dataa, 28. lokakuuta 2026. Hallintapaneelin vaiheet on lainattu Supabasen dokumentaatiosta, ei klikattu läpi. Hallintapaneelin oman roolin luomilla tauluilla voi olla toiset oletusgranttit. En testannut sitä enkä Storagea, Realtimea tai GraphQL:ää.

Etkö ole varma, ovatko taulusi lukittu?

Katson mielelläni skeemaa kanssasi. Ei myyntipuhetta.

[Ota yhteyttä](https://madsalt.tech/fi/#contact)
