Learn

Base de données et Row Level Security

Supabase

Créer une table, écrire dedans, et bloquer les accès avec RLS.

Supabase n'est pas un "CRUD magique" qui cache une base de données quelque part. Derrière, tu as un vrai PostgreSQL, avec toute sa puissance et toutes ses responsabilités. Dans cette leçon, tu vas créer ta première table, écrire et lire des données via le SQL Editor, puis activer Row Level Security (RLS) pour que chaque utilisateur ne voie que ce qui lui appartient.

SQL en 2 minutes

Si tu as déjà touché au SQL, tu peux passer directement à la section suivante. Sinon, voici les quatre instructions dont tu vas avoir besoin dans cette leçon.

PostgreSQL est une base de données relationnelle : les données sont organisées en tables (lignes + colonnes), et le SQL est le langage pour les manipuler.

sql
-- Créer une table "posts" avec trois colonnes
CREATE TABLE posts (
  id         bigint generated always as identity primary key,
  user_id    uuid references auth.users(id),
  content    text not null
);

-- Insérer une ligne
INSERT INTO posts (user_id, content)
VALUES ('123e4567-...', 'Mon premier post');

-- Lire toutes les lignes
SELECT * FROM posts;

-- Lire uniquement les posts d'un utilisateur précis
SELECT * FROM posts WHERE user_id = '123e4567-...';

Quatre instructions, quatre cas d'usage. C'est tout ce dont tu as besoin pour démarrer. Un cours dédié couvrira SQL de façon plus complète :

À côté · fullstackfullstack / base-de-donnees-introAller plus loin sur les bases de données relationnelles et le SQL dans le contexte fullstack

Le SQL Editor de Supabase

Avant de créer la table, voyons où écrire ce SQL. Dans ton dashboard Supabase, le menu de gauche contient un item SQL Editor (icône de terminal ou de code). C'est un éditeur interactif connecté directement à ton instance PostgreSQL.

Tu peux y coller n'importe quelle instruction SQL et la lancer avec Run (ou Cmd+Enter / Ctrl+Enter). Les résultats s'affichent dans la moitié basse de l'écran.

Créer ta première table

Colle ce bloc dans le SQL Editor et exécute-le. Il crée une table posts avec un identifiant auto-généré, une référence vers l'utilisateur propriétaire, et un champ texte.

sql
CREATE TABLE posts (
  id         bigint generated always as identity primary key,
  user_id    uuid references auth.users(id) on delete cascade,
  content    text not null,
  created_at timestamptz default now()
);

La colonne user_id pointe vers la table auth.users gérée par Supabase. La clause on delete cascade signifie que si un compte est supprimé, ses posts le sont aussi automatiquement.

Le problème sans RLS

Teste maintenant ce que n'importe quel client peut faire avec la clé publishable (anon key) que tu as copiée dans ton .env :

sql
SELECT * FROM posts;

Sans RLS activé, cette requête retourne tous les posts de tous les utilisateurs. Ton app frontend utilise la clé anon par défaut, et par défaut Supabase laisse passer tout le trafic vers les tables que tu as créées.

Ce n'est pas un bug, c'est le comportement attendu quand RLS est désactivé : Supabase fait confiance à ton code pour filtrer correctement. Le problème : un utilisateur mal intentionné peut appeler l'API directement avec ta clé anon et vider la table entière.

Activer RLS sur ta table

Une seule instruction SQL suffit :

shell
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

Exécute-la dans le SQL Editor. Maintenant, relance SELECT * FROM posts; : tu obtiens zéro résultat, même si des lignes existent. Ce n'est pas une erreur. C'est le comportement attendu : RLS activé sans aucune policy = tout est bloqué par défaut.

C'est une approche "deny-first" : tu pars d'un accès zéro, puis tu ouvres explicitement ce que tu veux autoriser.

Écrire ta première policy SELECT

Une policy RLS est une règle SQL attachée à une table qui s'applique automatiquement comme une clause WHERE invisible. Tu en crées une avec CREATE POLICY.

Voici la policy qui autorise un utilisateur connecté à lire uniquement ses propres posts :

shell
CREATE POLICY "users can read their own posts"
ON posts FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = user_id);

Décortiquons cette instruction :

  • ON posts FOR SELECT : s'applique aux lectures sur la table posts.
  • TO authenticated : uniquement pour les utilisateurs connectés (rôle authenticated). Les visiteurs anonymes (rôle anon) ne sont pas concernés par cette policy et ne voient toujours rien.
  • USING (...) : la condition que chaque ligne doit satisfaire pour être retournée. Ici, auth.uid() renvoie l'UUID de l'utilisateur authentifié par le JWT dans la requête. Une ligne n'est retournée que si son user_id correspond.

Policies pour écrire, modifier, supprimer

La même logique s'applique aux autres opérations. Voici les trois policies complémentaires :

sql
-- Insérer : l'utilisateur ne peut créer des posts qu'en son propre nom
CREATE POLICY "users can insert their own posts"
ON posts FOR INSERT
TO authenticated
WITH CHECK ((SELECT auth.uid()) = user_id);

-- Modifier : seulement ses propres posts, et sans changer le user_id
CREATE POLICY "users can update their own posts"
ON posts FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = user_id)
WITH CHECK ((SELECT auth.uid()) = user_id);

-- Supprimer : seulement ses propres posts
CREATE POLICY "users can delete their own posts"
ON posts FOR DELETE
TO authenticated
USING ((SELECT auth.uid()) = user_id);

Remarque la différence entre USING et WITH CHECK :

  • USING s'applique aux lignes existantes : "est-ce que l'utilisateur a le droit d'accéder à cette ligne ?"
  • WITH CHECK s'applique aux lignes nouvelles ou modifiées : "est-ce que la ligne telle qu'elle sera après l'opération respecte la règle ?"

Pour une UPDATE, les deux clauses ensemble garantissent que l'utilisateur possède la ligne avant de la modifier, et qu'il ne peut pas changer son user_id pour s'approprier un post qui ne lui appartient pas.

Tester l'isolation entre utilisateurs

Le SQL Editor te permet de simuler des requêtes en tant qu'un utilisateur précis. C'est utile pour vérifier que RLS fonctionne comme attendu avant de tester depuis ton app.

Dans le SQL Editor, tu peux définir le rôle et l'utilisateur courant via SET :

sql
-- Simuler une requête d'un utilisateur authentifié (remplace l'UUID par un vrai)
SET role authenticated;
SET request.jwt.claims = '{"sub": "uuid-de-ton-utilisateur"}';

SELECT * FROM posts;

Si tout est bien configuré, tu ne verras que les posts dont le user_id correspond à l'UUID fourni.

Supabase · Row Level Security

Sources

À côté

À côté · fullstackLes bases de données

Concepts-ponts

Concept-pont · SQL et modele relationnel

De la theorie SQL (tables, lignes, cles, joins) a son application concrete dans Supabase (policies RLS, queries via JS client).

Coche les étapes pour débloquer la suite

Retour au cours