Quand une requete Supabase echoue, beaucoup d'equipes disent tout de suite : "c'est la RLS". Parfois oui. Mais tres souvent, le vrai probleme est plus haut : le role n'a meme pas le droit d'atteindre la table, donc la RLS n'entre pas encore en jeu.
La distinction la plus utile a retenir est celle-ci :
GRANT: le role peut-il atteindre la table, la vue ou la fonction via la Data API ?- RLS : une fois la table atteinte, quelles lignes peut-il lire, inserer, modifier ou supprimer ?
Si vous confondez ces deux couches, vous perdez du temps et vous risquez de corriger la mauvaise chose.
Le raccourci le plus clair
Pensez a une porte et a un filtre.
- le
GRANT, c'est la porte d'entree - la RLS, c'est le filtre a l'interieur
Sans GRANT, la porte reste fermee. Avec un GRANT mais sans policy adaptee, vous entrez peut-etre dans la piece, mais vous ne pouvez rien voir ou rien faire.
Ce que dit Supabase
La documentation Supabase est tres claire sur ce point : la Data API repose sur deux couches qui travaillent ensemble.
- Les
GRANTdeterminent quels roles Postgres peuvent atteindre un objet. - Les policies RLS determinent quelles lignes ces roles peuvent lire ou modifier.
C'est exactement pour cela qu'une table peut etre :
- accessible mais trop ouverte
- accessible mais vide
- inaccessible malgre des policies correctes
Le cas numero 1: le GRANT manque
C'est le cas le plus simple a diagnostiquer. Votre requete renvoie une erreur de permission, souvent avec le code 42501.
Exemple typique :
- vous creez une table
public.projects - vous activez la RLS
- vous ajoutez une policy propre
- mais vous oubliez le
grant select on table public.projects to authenticated;
Resultat : la policy peut etre parfaite, l'utilisateur n'atteint pas la table.
C'est pour cela que la documentation Supabase recommande de traiter GRANT et RLS dans la meme migration.
Le cas numero 2: le GRANT existe, mais la RLS bloque tout
Ici, la table est accessible, mais la lecture ou l'ecriture reste impossible parce que la policy ne laisse passer aucune ligne utile.
PostgreSQL le documente tres clairement : quand la row security est activee et qu'aucune policy ne correspond, une logique default deny s'applique.
En pratique, cela donne souvent des symptomes comme :
- une requete qui ne renvoie aucune ligne alors qu'il y a bien des donnees
- une insertion refusee par la policy
- un update qui ne touche rien
Dans ce cas, le GRANT n'est pas en cause. C'est bien la RLS qu'il faut revoir.
Le cas numero 3: le GRANT existe, mais la table est trop ouverte
L'inverse existe aussi. Une table peut etre joignable via la Data API avec des privileges trop larges, surtout sur des projets historiques qui ont garde des defaults permissifs.
Si la RLS n'est pas activee, ou si la policy est trop large, vous exposez plus de donnees que prevu.
C'est souvent plus dangereux qu'une erreur visible, parce que tout semble "marcher". Pourtant, le vrai probleme est une exposition trop genereuse.
Un exemple concret
Imaginons une table public.messages avec une colonne user_id.
Objectif produit :
- un visiteur non connecte ne voit rien
- un utilisateur connecte peut lire ses propres messages
- le back-end serveur peut gerer l'ensemble
Le schema minimal ressemble a ceci :
grant usage on schema public to anon, authenticated, service_role;
grant select, insert on table public.messages to authenticated; grant select, insert, update, delete on table public.messages to service_role;
alter table public.messages enable row level security;
create policy "users can read their own messages" on public.messages for select to authenticated using (auth.uid() = user_id);
create policy "users can create their own messages" on public.messages for insert to authenticated with check (auth.uid() = user_id); ```
Dans cet exemple :
- sans les
GRANT, l'utilisateur n'atteint pas la table - sans les policies, l'utilisateur atteint la table mais ne devrait pas voir n'importe quoi
- sans la RLS activee, vous prenez le risque d'un acces trop large
Tableau simple pour ne plus se tromper
| Situation | GRANT | RLS | Resultat | | --- | --- | --- | --- | | Table inaccessible | absent ou insuffisant | correcte ou non | erreur de permission, souvent 42501 | | Table accessible mais vide | present | trop stricte ou absente selon le cas | zero ligne ou refus sur l'action | | Table accessible et trop ouverte | trop large | absente ou trop large | fuite de donnees possible | | Table bien securisee | adapte | adaptee | acces correct, limite au besoin produit |
Comment diagnostiquer vite
Quand une requete pose probleme, ne partez pas au hasard. Suivez cet ordre :
- Est-ce que le role a un
GRANTsur l'objet ? - Est-ce que la table a la RLS activee ?
- Est-ce qu'une policy couvre bien l'action demandee ?
- Est-ce que la condition de la policy correspond vraiment a vos donnees ?
Cette methode evite de passer une heure sur une policy alors que la table n'est meme pas atteignable.
Le piege classique avec service_role
Le role service_role doit rester reserve a du code serveur de confiance. Ce n'est pas une rustine pour debloquer un probleme client.
Si vous mettez service_role cote navigateur, vous contournez le modele de securite du projet au lieu de le corriger. Ce n'est jamais la bonne solution pour un site public ou un portail client.
Une bonne regle de migration
Si une table doit etre exposee via la Data API, regroupez toujours ces 4 blocs dans la meme migration :
- creation de la table
GRANTexplicites- activation de la RLS
- policies
Cette discipline force une securite lisible. Et surtout, elle evite les situations ou une table "fonctionne" en local ou en admin, puis casse dans le vrai parcours utilisateur.
Ce qu'il faut retenir
La difference entre GRANT et RLS n'est pas theorique. C'est souvent elle qui decide si votre API est :
- inaccessible
- trop ouverte
- ou enfin correcte
Le GRANT ouvre la porte. La RLS filtre les lignes. Tant que cette distinction n'est pas claire, vous risquez de corriger le mauvais probleme.
