Aurabase Logo
aurabasedocs
docsGuidesRow-Level Security

Row-Level Security en production

Trois patterns RLS couvrant 95% des cas d'usage : ownership, multi-tenant, org hiérarchique. Avec tests pg_prove et debug.

12 min de lecture·Niveau avancé·Révisé le 15 avr. 2026
#
Rappel

Qu’est-ce que RLS

§ 01

Row-Level Security est une feature native PostgreSQL qui filtre les lignes retournées (ou acceptées) à la volée, en évaluant une expression SQL qui peut dépendre du rôle courant et de claims JWT. Aurabase injecte les claims user via request.jwt.claims et expose les helpers auth.uid(), auth.role(), auth.tenant(), auth.has_role().

#
Pattern 1

Ownership — chaque user ses propres lignes

§ 02

Cas d'usage : profils, notes, posts où chaque ligne appartient à un utilisateur unique via user_id ou author_id.

migrations/posts_rls.sql
SQL
alter table posts enable row level security;
create policy "posts_own_read" on posts
for select to authenticated
using (auth.uid() = author_id);
create policy "posts_own_write" on posts
for all to authenticated
using (auth.uid() = author_id)
with check (auth.uid() = author_id);
Astuce
Combinez for all avec using + with check pour couvrir SELECT/UPDATE/DELETE en une policy. L'INSERT utilise seulement with check.
#
Pattern 2

Multi-tenant — scope par organisation

§ 03

Cas d'usage : B2B SaaS où chaque user appartient à une organisation, et voit toutes les données de son org. Le JWT contient tenant, le helper auth.tenant() le retourne.

migrations/tenant_rls.sql
SQL
create policy "orders_tenant_scope" on orders
for all to authenticated
using (tenant_id = auth.tenant())
with check (tenant_id = auth.tenant());
-- Index indispensable pour la perf
create index idx_orders_tenant on orders(tenant_id);
#
Pattern 3

Org hiérarchique — permissions par rôle

§ 04

Cas d'usage : SaaS où les rôles dans une org (admin, member, viewer) ont des permissions différentes. On combine auth.tenant() avec auth.has_role().

migrations/org_rls.sql
SQL
create policy "invoices_read" on invoices
for select to authenticated
using (
tenant_id = auth.tenant()
and (auth.has_role('admin') or auth.has_role('billing'))
);
create policy "invoices_write" on invoices
for all to authenticated
using (tenant_id = auth.tenant() and auth.has_role('admin'))
with check (tenant_id = auth.tenant());
#
Tests

pg_prove — une assertion par policy

§ 05

Les policies sont du code. On les teste. pgTAP + pg_prove tournent en CI sur chaque migration.

tests/rls_posts.sql
SQL
begin;
select plan(4);
-- Seed
select tests.create_supabase_user(''alice@ex.com'');
select tests.create_supabase_user(''bob@ex.com'');
-- Alice crée un post
select tests.authenticate_as('alice@ex.com');
insert into posts(author_id, title) values (auth.uid(), 'hello');
-- Bob ne la voit pas
select tests.authenticate_as('bob@ex.com');
select is_empty('select * from posts', 'Bob ne voit pas les posts d\'Alice');
select * from finish();
rollback;
#
Debug

Quand la policy ne fait pas ce que vous voulez

§ 06
  • RLS désactivée ? Vérifiez avec select relrowsecurity from pg_class where relname = 'posts'
  • Policies qui s'additionnent : multiple policies sur même table + opération + rôle combinent en OR. Restreignez avec AND dans une seule policy si besoin
  • Permissions GRANT manquantes : même avec une policy, le rôle doit avoir grant select on posts to authenticated
  • Claims non injectés : depuis Postgres, select current_setting('request.jwt.claims', true) — null signifie que vous êtes en direct DB, pas via le gateway
Dernière mise à jour · 15 avr. 2026