Vizion Web
Auth & Sécurité

RLS

Définition

Row Level Security. Mécanisme PostgreSQL qui filtre automatiquement les lignes d'une table selon l'utilisateur connecté. Pierre angulaire d'un multi-tenant sécurisé sur Supabase.

Comment ça marche

La Row Level Security (RLS) est un mécanisme PostgreSQL qui filtre automatiquement les lignes d'une table selon le contexte de l'utilisateur connecté. On active RLS sur une table (ALTER TABLE ... ENABLE ROW LEVEL SECURITY), puis on définit des policies en SQL : "un utilisateur ne voit que les lignes où user_id correspond à son auth.uid()". À chaque requête SELECT, UPDATE, INSERT ou DELETE, PostgreSQL applique automatiquement ces policies, indépendamment du code applicatif. Si une ligne ne passe pas la policy, elle est invisible pour cette session, comme si elle n'existait pas. Le filtre vit au niveau de la base, pas du code.

À quoi ça sert

La RLS résout le problème de l'isolation des données dans un SaaS multi-tenant. Sans RLS, chaque requête applicative doit ajouter manuellement WHERE organization_id = 'X', avec le risque qu'un développeur oublie ce filtre sur une requête et expose les données d'un autre client. Avec RLS, on définit la règle une fois côté base : impossible de l'oublier, impossible de la contourner depuis le code. C'est ce qui rend possible de connecter Supabase directement depuis le frontend sans backend custom : la base est la couche de sécurité, le code applicatif ne fait que des requêtes simples.

Comment l'écrire

Une policy RLS s'écrit en SQL standard : CREATE POLICY "users_isolation" ON tasks FOR SELECT USING (user_id = auth.uid()). On crée des policies séparées par opération (SELECT, INSERT, UPDATE, DELETE), ou une seule policy FOR ALL si le critère est identique. La clause USING filtre les lignes visibles. WITH CHECK valide les insertions et modifications (empêche un utilisateur de créer une ligne avec user_id différent du sien). On utilise des fonctions auth (auth.uid(), auth.role(), auth.jwt()) pour accéder au contexte utilisateur. Pour des cas complexes, on définit des fonctions SQL réutilisables.

Le rôle dans Supabase

Supabase repose massivement sur la RLS. PostgREST expose la base directement via REST, et chaque requête est exécutée dans le contexte du JWT utilisateur. Sans RLS activée, n'importe qui peut tout lire et tout modifier. Avec RLS, on contrôle finement qui voit quoi. Le service_role JWT (à utiliser uniquement côté serveur) bypass RLS, ce qui sert pour les opérations administratives. Le anon JWT (utilisable côté client) est soumis à RLS, ce qui sert pour les opérations utilisateur. Cette distinction est fondamentale : on ne mélange jamais les deux dans le même code.

Quand l'utiliser

La RLS est obligatoire pour tout SaaS multi-tenant exposé à des utilisateurs finaux. Sans elle, l'isolation des données dépend du code, et le code finit toujours par contenir des bugs. Pour un outil interne avec un seul tenant et accès uniquement via backend custom, la RLS est moins critique : on peut filtrer dans le code, le risque est limité. Mais sur du SaaS B2B, c'est non négociable. Pour Supabase, RLS est activée par défaut sur les nouvelles tables : ne pas avoir de policy équivaut à "personne ne voit rien", ce qui est plus sûr que l'inverse.

Les performances

Une policy RLS est ajoutée à chaque requête comme un filtre WHERE supplémentaire. Sur des policies simples (user_id = auth.uid()), l'impact est négligeable, surtout avec un index sur user_id. Sur des policies complexes (jointures, sous-requêtes, fonctions), l'impact peut devenir significatif. On profile les requêtes lentes avec EXPLAIN et on optimise (indexer les colonnes utilisées par la policy, simplifier la logique). Supabase a publié des guides précis sur l'optimisation RLS, et certains patterns (utilisation de SECURITY DEFINER functions) permettent de contourner les cas pathologiques sans sacrifier la sécurité.

Les pièges à éviter

Le piège majeur : croire que tester en compte admin valide les policies. Il faut tester avec un compte utilisateur réel et vérifier que l'isolation fonctionne. Le piège du "forgot to enable" : créer une table sans ENABLE ROW LEVEL SECURITY et exposer toutes les données. On a un script qui audite toutes les tables et alerte sur celles sans RLS. Le piège du service_role en client : utiliser la clé service_role côté frontend bypass tout RLS, c'est une catastrophe de sécurité. Le piège des fonctions SQL : une fonction SECURITY DEFINER bypass RLS dans son body, on l'utilise sciemment et on documente. Et toujours créer des policies pour CHAQUE opération (SELECT, INSERT, UPDATE, DELETE), pas seulement SELECT.

Autres termes de la catégorie Auth & Sécurité