Supabase RLS uitgelegd voor Lovable-bouwers — met een template die je vandaag kunt kopiëren
Row Level Security is de belangrijkste knop die je moet begrijpen zodra je met echte gebruikers werkt. Menselijke uitleg, plus de drie policies die je bijna altijd nodig hebt.
Maarten Vermeulen
Bijdrager · lvbl.nl
De grootste fout die ik in bijna elke Lovable-app zie: RLS staat wel aan, maar er staat geen policy op. De tabel is in feite onzichtbaar voor je gebruikers. De agent zet RLS meestal keurig aan bij nieuwe tabellen (dat is de veilige default), maar vergeet soms een SELECT-policy toe te voegen. Symptoom: je code geeft geen error, maar je lijstje blijft leeg. Fix: expliciet vragen om de policies mét de tabel.
Drie policies die je bijna altijd nodig hebt bij een user-facing tabel met een user_id-kolom. Eén, SELECT: gebruikers zien alleen hun eigen rijen. `CREATE POLICY "eigen rijen zien" ON public.notes FOR SELECT USING (auth.uid() = user_id);`. Twee, INSERT: gebruikers kunnen alleen rijen invoegen die aan zichzelf gekoppeld zijn. `CREATE POLICY "eigen rijen aanmaken" ON public.notes FOR INSERT WITH CHECK (auth.uid() = user_id);`. Drie, UPDATE en DELETE: alleen op eigen rijen. `CREATE POLICY "eigen rijen beheren" ON public.notes FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);`.
De grap zit in het onderscheid tussen USING en WITH CHECK. USING bepaalt welke bestaande rijen zichtbaar zijn (voor SELECT, UPDATE, DELETE). WITH CHECK bepaalt welke nieuwe of gewijzigde rijen mogen bestaan (voor INSERT en UPDATE). Als je alleen USING gebruikt op een UPDATE, kan een gebruiker in theorie zijn eigen rij overschrijven met een andere user_id — dus jouw rij van je stelen. Altijd allebei zetten.
Fout twee die ik vaak zie: policies die naar `public.users` kijken in plaats van naar `auth.uid()`. Supabase splitst users bewust: er is een privé `auth.users`-tabel die de identiteit bijhoudt, en een publiek `public.profiles` (of `public.users`) waarin jij vrijblijvend extra data zet. `auth.uid()` is de veilige, snelle referentie. Ga niet in policies via public.users joinen — dat kost performance én introduceert bugs.
Fout drie: te ruime GRANTs. Sinds vorig jaar zet Supabase de default privileges op public schema restrictiever — je moet expliciet `GRANT SELECT, INSERT, UPDATE, DELETE ON public.notes TO authenticated;` uitvoeren voor iedere tabel. Lovable's agent doet dat inmiddels netjes, maar als je zelf een migratie schrijft: vergeet 'm niet, anders krijg je permission-denied errors die niets met RLS te maken hebben.
Bonus: rollen los van je user-tabel. Rollen als "admin" of "moderator" hoor je niet op je profielen-tabel te zetten — dat is een klassieke privilege escalation. De veilige aanpak (staat ook in de officiële Supabase-guides): maak een aparte `user_roles`-tabel, en een security-definer-functie `has_role(user_id, role)`. Roep die in je policies aan. Zo kunnen gebruikers hun eigen rol nooit updaten, zelfs al zouden ze hun profiel-record kunnen aanraken.
Checklist voor elke nieuwe tabel: (1) heeft de tabel een user_id-kolom? (2) staat RLS aan? (3) is er een SELECT-policy? (4) staan USING én WITH CHECK op INSERT/UPDATE? (5) zijn de GRANTs op de tabel gezet? Loop deze vijf punten letterlijk af — het scheelt je een debug-avond.
Bronnen: Supabase Docs (Row Level Security, User Management), Lovable's eigen prompts-repository, ervaring uit ~20 review-sessies van community-projecten.
Vond je dit fijn?
Geef een hartje en deel het met een andere Lovable-fan.