Je database staat open en je app ziet er prima uit
Row Level Security: waarom je Supabase-tabel voor iedereen leesbaar is en hoe je dat in vier regels SQL dichtzet.
- SoortStappenplan
- Tijd30 minuten
- Voor wieIedereen die Supabase, Firebase of een vergelijkbare dienst gebruikt
- Fases5
Bij Supabase praat de browser rechtstreeks met de database in plaats van via jouw server. Dat is geen fout maar het ontwerp, en het is precies waarom je in één avond iets werkends hebt. Het gevolg is wel dat de sleutel die daarvoor nodig is, de anon key, gewoon in je JavaScript staat. Die sleutel houdt op zichzelf niets tegen. Wat wél tegenhoudt is Row Level Security: regels per rij die bepalen wie wat mag zien. En op een nieuwe tabel staat RLS standaard uit.
Deze handleiding is geschreven om als werkopdracht te dienen. Neem hem in zijn geheel mee naar Cursor, Claude Code of waar je ook bouwt, en werk hem fase voor fase af.
Waar het misgaat
Fase 0Vaststellen
Twintig seconden werk, en daarna weet je of dit jouw probleem is.
- Open je Supabase-dashboard, ga naar Advisors en dan naar Security Advisor. Elke tabel zonder RLS staat daar met een rode melding. Dit is de snelste controle die er is.
- Wil je het met eigen ogen zien: open je site, druk op F12, ga naar het tabblad Network en herlaad de pagina. Zoek een verzoek naar jouw-project.supabase.co; daar staat de anon key in.
- Open een privévenster en doe met die sleutel een verzoek naar een tabel waar je niets te zoeken hebt. Krijg je rijen van andere gebruikers terug, dan is dit jouw probleem.
- Doe dit uitsluitend op je eigen project. Op andermans database is dit geen controle maar een inbraak.
Fase 1De deur dicht
RLS aanzetten per tabel. Hierna kan niemand meer iets, inclusief je eigen app. Dat is de bedoeling.
Doe dit tabel voor tabel en niet in één keer voor je hele database. Je wilt per tabel kunnen zien wat er stukgaat, want dat vertelt je precies welke regels je nog moet schrijven.
alter table public.projecten enable row level security;
Fase 2Gericht openzetten
Vier regels voor het normale geval: een tabel waarin elke rij bij één gebruiker hoort.
-- Elke rij hoort bij één gebruiker via de kolom gebruiker_id. create policy "eigen rijen lezen" on public.projecten for select using (auth.uid() = gebruiker_id); create policy "zelf aanmaken" on public.projecten for insert with check (auth.uid() = gebruiker_id); create policy "eigen rijen bijwerken" on public.projecten for update using (auth.uid() = gebruiker_id) with check (auth.uid() = gebruiker_id); create policy "eigen rijen verwijderen" on public.projecten for delete using (auth.uid() = gebruiker_id);
Het verschil tussen using en with check
Dit is het stuk dat bijna iedereen overslaat en waar het daarna alsnog misgaat. `using` bepaalt welke rijen je mag zien of aanraken. `with check` bepaalt hoe een rij eruit mag zien nadat je hem hebt geschreven.
Laat je bij update de `with check` weg, dan mag een gebruiker zijn eigen rij pakken en er een ander gebruiker_id in zetten. Hij geeft zijn rij dan weg aan iemand anders, of schuift hem naar een account dat hij zelf ook beheert. Zet ze allebei.
Fase 3De drie dingen die RLS niet oplost
Zodat je niet denkt dat je klaar bent terwijl er nog een deur openstaat.
| Wat | Waarom RLS niet helpt | Wat wel |
|---|---|---|
| De service role key | Die sleutel gaat langs alle regels heen, dat is zijn doel | Nooit in de browser, alleen op de server, en nooit in je repository |
| Storage-buckets | Bestanden hebben eigen regels, los van je tabellen | Zet de bucket op private en schrijf er apart beleid voor |
| Je eigen API-routes | Die draaien op de server en zijn vaak precies de plek waar je de regels omzeilt | Controleer daar zelf wie de aanvrager is, elke route opnieuw |
Fase 4Controleren
Bewijs voordat je zegt dat het klaar is. Niet omdat het hoort, maar omdat je dit lek niet aan je app kunt zien.
- Security Advisor staat schoonGeen enkele tabel meldt nog dat RLS uit staat. Dit is de meting, niet je gevoel.
- Je eigen app werkt nogKlik door alles waar je gegevens laadt of opslaat. Een fout van het type new row violates row-level security policy betekent dat je insert-regel ontbreekt of niet klopt.
- Een tweede account ziet de eerste nietMaak een testgebruiker aan, log in, en controleer dat je alleen je eigen rijen ziet. Dit is de enige controle die het echte gedrag bewijst.
- Update kan de eigenaar niet veranderenProbeer met de testgebruiker een rij bij te werken en er een ander gebruiker_id in te zetten. Dat moet weigeren. Lukt het wel, dan mist je with check.
- De service role key staat niet in je frontendZoek in je hele project op service_role. Elke treffer buiten je servercode is een probleem.
Stond een tabel al langer open, dan is dichtzetten niet genoeg. Je weet niet wie er langs is geweest, dus behandel de gegevens als gelekt en beoordeel of je gebruikers moet informeren.
Alles hierboven in één keer, inclusief de fases, de SQL en de controlestap.
Werkt dit, maar wil je het niet zelf doen?
Dit blad is gratis en blijft gratis. Wil je dat het bij jou automatisch draait in plaats van met de hand, dan bouw ik dat. Beschrijf in vijf vragen wat er vastloopt en stuur het adres van je app mee; binnen één werkdag heb je mijn antwoord op papier.
Vertel wat er vastloopt