Aller au contenu principal

Mise en place des RLS

Sécuriser l’accès aux données selon le rôle de l’utilisateur

Notions théoriques

Qu’est-ce que le Row Level Security (RLS) ?

Le Row Level Security (RLS) est une fonctionnalité de PostgreSQL (et donc de Supabase) qui permet de restreindre l’accès aux lignes d’une table en fonction de l’utilisateur connecté.

Il ne s’agit pas seulement de masquer des colonnes ou de bloquer des tables entières, mais bien de filtrer ligne par ligne ce qu’un utilisateur peut voir, insérer, modifier ou supprimer.

attention

RLS est un outil essentiel pour éviter les fuites de données dans les applications multi-utilisateurs.

RLS doit être activé explicitement sur chaque table où l’on souhaite l’utiliser.


Pourquoi activer RLS ?

Par défaut, toutes les lignes d’une table sont visibles si un utilisateur a la permission de faire un SELECT.

Cela pose problème dans une application avec différents rôles (guest, student, teacher, admin, super-admin).

Avec RLS, on peut dire :

  • un guest ne voit que son propre profil,
  • un student ne peut écrire des messages que dans ses classes,
  • un teacher peut voir les membres de ses classes,
  • un admin peut modifier toutes les classes,
  • un super-admin peut tout faire.

Activer RLS sur une table

Pour activer RLS sur une table dans Supabase :

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
attention

Supabase n’applique aucune restriction par défaut après activation.

Il faut ajouter des politiques (POLICY) pour définir ce qui est autorisé.


Ajouter une politique RLS

Une politique est une règle SQL qui s’applique à un type d’opération : SELECT, INSERT, UPDATE, ou DELETE.

Exemple : autoriser un utilisateur à voir son propre profil :

create policy "Un utilisateur peut voir son propre profil"
on profiles
for select
using (auth.uid() = id);

Explication :

  • auth.uid() est l’ID de l’utilisateur connecté.
  • id est la clé primaire de profiles, qui référence auth.users.id.

Exemple de politique INSERT : écrire un message uniquement si on est membre d’une classe

create policy "Un utilisateur peut écrire un message dans une classe à laquelle il appartient"
on messages
for insert
with check (
exists (
select 1 from class_members
where class_id = messages.class_id
and user_id = auth.uid()
)
);

Tester les politiques

Il est possible de simuler un utilisateur dans l’éditeur SQL Supabase :

set role authenticated;
set local jwt.claims.sub = 'uuid-de-l-utilisateur';

Cela permet de tester les effets d’une politique comme si on était connecté avec un rôle donné.


Bonnes pratiques avec RLS

astuce

Toujours activer RLS avant de déployer une application multi-utilisateurs.

info

Créer des politiques pour chaque opération (SELECT, INSERT, UPDATE, DELETE).

remarque

Tester les politiques avec différents rôles pour éviter les fuites accidentelles.

attention

Ne jamais supposer que les utilisateurs vont se limiter à l’interface prévue. Ils peuvent envoyer des requêtes SQL directement (via API ou outils comme DBeaver).

Exemple pratique

Il est possible de sécuriser la table profiles pour que :

  • un utilisateur ne voie que son propre profil,
  • un admin ou super-admin voie tous les profils.

Étape 1 : activer RLS sur la table profiles

alter table profiles enable row level security;

Étape 2 : autoriser un utilisateur à voir son propre profil

create policy "lecture de son propre profil"
on profiles
for select
using (auth.uid() = id);

Étape 3 : autoriser les admins à voir tous les profils

create policy "lecture pour les admins"
on profiles
for select
using (
exists (
select 1 from user_roles
where user_id = auth.uid()
and role in ('admin', 'super-admin')
)
);

Étape 4 : tester avec un utilisateur simulé

set role authenticated;
set local jwt.claims.sub = 'uuid-du-student';

select * from profiles;

Résultat : l’étudiant ne voit que sa propre ligne.

Changer l’UUID pour celui d’un admin : il verra tous les profils.


Test de mémorisation/compréhension


Quel est le rôle de la commande 'alter table ... enable row level security' ?


Que permet la fonction auth.uid() dans une politique RLS ?


Quelle clause est utilisée dans une politique RLS pour autoriser la lecture ?


Quelle clause est utilisée pour valider une insertion dans une politique RLS ?


Quelle commande permet de simuler un utilisateur dans Supabase ?


Quelle est la conséquence d’activer RLS sans ajouter de politique ?


Comment autoriser un admin à voir tous les profils ?


Quelle commande permet de tester le rôle 'authenticated' ?


Quel champ relie les rôles aux utilisateurs dans user_roles ?


Pourquoi faut-il tester les RLS avec des UUID différents ?



TP pour réfléchir et résoudre des problèmes

Ce TP a pour objectif de mettre en place des politiques Row Level Security (RLS) sur les tables existantes de votre BD Supabase, en appliquant des restrictions d'accès selon les rôles définis dans la table user_roles.

Étape 1 : Activer RLS sur la table profiles

Dans l’éditeur SQL de Supabase, activer RLS sur la table profiles.

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
Une solution

Étape 2 : Créer une politique RLS pour permettre à un utilisateur de voir uniquement son propre profil

Créer une politique SELECT qui autorise un utilisateur à voir uniquement sa propre ligne dans la table profiles.

CREATE POLICY "lecture de son propre profil"
ON profiles
FOR SELECT
USING (auth.uid() = id);
Une solution

Étape 3 : Créer une politique RLS permettant aux administrateurs de voir tous les profils

Créer une politique SELECT qui autorise les utilisateurs ayant le rôle admin ou super-admin à voir tous les profils.

CREATE POLICY "lecture pour les admins"
ON profiles
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM user_roles
WHERE user_id = auth.uid()
AND role IN ('admin', 'super-admin')
)
);
Une solution

Étape 4 : Tester les politiques avec un utilisateur simulé

Simuler une session avec un utilisateur ayant un rôle student (ou autre) pour vérifier que les politiques fonctionnent.

SET ROLE authenticated;
SET LOCAL jwt.claims.sub = 'uuid-de-l-utilisateur';
SELECT * FROM profiles;

Changer l’UUID pour tester différents cas :

  • UUID d’un guest → ne doit voir que sa ligne
  • UUID d’un admin → doit voir tous les profils
Une solution

Étape 5 : Créer une politique RLS pour permettre aux utilisateurs de modifier uniquement leur propre profil

Créer une politique UPDATE qui autorise un utilisateur à modifier uniquement sa propre ligne.

CREATE POLICY "modification de son propre profil"
ON profiles
FOR UPDATE
USING (auth.uid() = id);
Une solution

Étape 6 : Créer une politique RLS permettant aux administrateurs de modifier tous les profils

Créer une politique UPDATE qui autorise les admin et super-admin à modifier n’importe quel profil.

CREATE POLICY "modification pour les admins"
ON profiles
FOR UPDATE
USING (
EXISTS (
SELECT 1 FROM user_roles
WHERE user_id = auth.uid()
AND role IN ('admin', 'super-admin')
)
);
Une solution

Étape 7 : créer une politique RLS pour interdire les suppressions sauf pour les super-admins

Créer une politique DELETE qui autorise uniquement les super-admin à supprimer un profil.

CREATE POLICY "suppression réservée aux super-admins"
ON profiles
FOR DELETE
USING (
EXISTS (
SELECT 1 FROM user_roles
WHERE user_id = auth.uid()
AND role = 'super-admin'
)
);
Une solution

Étape 8 : Tester les politiques UPDATE et DELETE avec différents utilisateurs

Simuler une session avec un student, un teacher, un admin et un super-admin. Tester les opérations suivantes :

  • UPDATE sur sa propre ligne
  • UPDATE sur une autre ligne
  • DELETE sur sa propre ligne
  • DELETE sur une autre ligne

Utiliser les commandes :

SET ROLE authenticated;
SET LOCAL jwt.claims.sub = 'uuid-de-l-utilisateur';

Puis effectuer une requête comme :

UPDATE profiles
SET username = 'nouveau_nom'
WHERE id = 'uuid-d-un-autre-user';

Et :

DELETE FROM profiles
WHERE id = 'uuid-d-un-autre-user';
Une solution

Étape 9 : sécuriser la table messages pour que seuls les membres d’une classe puissent y insérer des messages

Créer une politique INSERT sur la table messages qui autorise l’insertion uniquement si l’utilisateur est membre de la classe indiquée.

CREATE POLICY "insertion de messages par les membres de la classe"
ON messages
FOR INSERT
WITH CHECK (
EXISTS (
SELECT 1 FROM class_members
WHERE class_id = messages.class_id
AND user_id = auth.uid()
)
);
Une solution

Étape 10 : Tester l’insertion de messages avec un utilisateur non membre de la classe

Simuler un utilisateur avec un UUID qui n’est pas membre de la classe ciblée, puis tenter une insertion :

INSERT INTO messages (content, class_id, sender_id)
VALUES ('test', 'uuid-classe', 'uuid-user');
Une solution

Étape 11 : Vérifier que les politiques RLS n’introduisent pas de fuites de données

Tester les requêtes suivantes avec un utilisateur guest :

SELECT * FROM profiles;
SELECT * FROM messages;

Puis vérifier que :

  • seules les lignes autorisées sont visibles,
  • aucune erreur n’est levée,
  • aucune fuite d’information n’est possible.
Une solution