Partie 1 — Le problème que tout fondateur SaaS doit comprendre

L’incident qui ne devrait jamais arriver

Imaginez la scène : Thomas dirige une PME, il utilise votre logiciel SaaS depuis six mois. Un matin, il se connecte à son tableau de bord et tombe sur une liste de contacts qui ne lui appartient pas — les prospects d’un concurrent direct, stockés dans le même outil que lui. Il vous appelle, furieux. Vous cherchez dans votre code. Vous trouvez : un filtre oublié, une ligne manquante dans une requête. Rien de malveillant. Juste une erreur humaine. Mais la confiance, elle, est déjà brisée.

Ce scénario n’est pas rare. Il arrive quand on construit un SaaS sans réfléchir dès le départ à l’isolation des données entre les clients.

Plusieurs clients, une seule base de données : le défi du multi-tenancy

Un SaaS, par définition, sert plusieurs clients — on les appelle des tenants (locataires, en anglais). Ils partagent la même application, les mêmes serveurs, parfois la même base de données. C’est ce qui rend le modèle économiquement viable : mutualiser les coûts d’infrastructure.

Un SaaS, c’est donc comme une sorte de colocation. Il y a des parties accessibles à l’ensemble des locataires, comme le hall d’entrée, les ascenseurs, mais il y a également des parties privées qu’il faut absolument sécuriser. Comment s’assurer que les données du client A ne sont jamais accessibles au client B ?

Il existe trois grandes approches pour répondre à cette question :

ApprochePrincipeCoûtSécuritéComplexité
Une base par clientChaque client a sa propre base de données isoléeÉlevéMaximaleForte (déploiement, maintenance)
Un schéma par clientMême base, mais des espaces séparés à l’intérieurMoyenBonneMoyenne
Même table, filtre par ID clientToutes les données cohabitent, séparées par un identifiantFaibleVariableFaible… mais risquée

La troisième approche — toutes les données dans les mêmes tables, triées par un identifiant client — est la plus répandue dans les SaaS modernes. Elle est moins coûteuse, plus simple à maintenir, et suffit amplement si elle est bien sécurisée. C’est elle que font tourner la majorité des outils que vous utilisez au quotidien.

Le risque caché : la sécurité dans le code, ça se casse

Avec cette approche, chaque requête vers la base de données doit inclure un filtre du type : “donne-moi uniquement les projets appartenant à ce client”. Ce filtre, c’est le développeur qui l’écrit, à la main, dans chaque requête.

Le problème ? Un développeur distrait, fatigué, ou simplement pressé, peut oublier ce filtre. Une fois. Sur une seule requête. Et c’est suffisant pour exposer les données d’un client à un autre.

La sécurité placée uniquement dans le code applicatif est fragile. Elle dépend de la rigueur humaine. Ce qu’il faut, c’est un filet de sécurité plus bas : au niveau de la base de données elle-même.


Partie 2 — Ce que Supabase apporte : RLS, le garde-fou dans la base

Supabase en trois lignes

Supabase est une plateforme open source qui fournit une base de données PostgreSQL managée, une API générée automatiquement, un système d’authentification, et des outils de stockage de fichiers. C’est l’un des choix les plus populaires pour construire un SaaS moderne rapidement, sans gérer soi-même l’infrastructure. Je l’utilise dans de nombreux projets.

Ce qui nous intéresse ici, c’est une fonctionnalité native de PostgreSQL qu’il expose facilement : la Row Level Security.

Row Level Security : la base qui se défend elle-même

La Row Level Security (RLS) — ou Sécurité au Niveau des Lignes — est un mécanisme qui permet de définir des règles directement dans la base de données pour contrôler qui peut lire ou modifier quelles lignes.

Concrètement : même si un développeur oublie un filtre dans son code, la base de données refusera de renvoyer des lignes qui n’appartiennent pas à l’utilisateur connecté. La base devient son propre garde-fou.

Analogie : imaginez un immeuble de bureaux où chaque locataire a son propre couloir avec une serrure. Peu importe qui tient la clé de l’immeuble ou qui ouvre la porte principale — sans la bonne clé de couloir, personne n’entre. Ce n’est pas le gardien qui protège les couloirs (il peut se tromper, être absent), c’est la serrure elle-même.

Pourquoi c’est supérieur à un filtre dans le code

Filtre dans le codeRow Level Security
Où se trouve la protectionDans l’applicationDans la base de données
Risque d’oubliOui, à chaque requêteNon, s’applique automatiquement
Applicable à tous les accèsNon (APIs externes, scripts…)Oui, quel que soit l’appelant
Confiance nécessaireDans chaque développeurDans la configuration initiale

Premiers éléments de code

Activer RLS sur une table se fait très simplement avec l’interface Supabase. Pour l’activer sur une table, en ligne de commande SQL :

-- On active le verrou sur la table "projects"
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

À partir de là, si vous réalisez une requête SELECT * FROM projects, même sans aucun filtre, la base ne renverra aucune ligne. C’est le principe du security by design. Par défaut, vous ne voyez aucune donnée et vous ne pouvez pas non plus les modifier. Pour voir des données, nous allons devoir ajouter des Policies.


Partie 3 — Mise en œuvre concrète : du modèle de données à la politique

B2B ou B2C : comment segmenter ?

La question de l’isolation des données ne se pose pas de la même façon selon votre modèle :

  • B2C (vous vendez à des particuliers) : chaque utilisateur est son propre tenant. L’identifiant de l’utilisateur suffit comme clé d’isolation. C’est simple.
  • B2B (vous vendez à des entreprises, avec plusieurs utilisateurs par client) : un tenant = une organisation. Plusieurs utilisateurs partagent les données de leur entreprise, mais pas celles des autres. C’est le cas le plus courant et le plus structurant.

Dans un contexte B2B, il faut une table intermédiaire qui relie chaque utilisateur à son organisation. C’est cette organisation — et non l’utilisateur — qui devient l’unité d’isolation.

Modèle de données type

Chaque table métier doit porter un champ organisation_id (ou tenant_id) qui fait le lien avec le propriétaire de la donnée.

┌─────────────────┐       ┌───────────────────┐       ┌───────────────────┐
│ organisations   │       │ members           │       │ projects          │
│─────────────────│       │───────────────────│       │───────────────────│
│ id              │◄──┐   │ id                │   ┌──►│ id                │
│ name            │   └───│ organisation_id   │   │   │ name              │
│ created_at      │       │ user_id           │   │   │ organisation_id   │
└─────────────────┘       │ role              │   │   │ created_at        │
                          └───────────────────┘   │   └───────────────────┘

                          ┌───────────────────┐   │
                          │ tasks             │   │
                          │───────────────────│   │
                          │ id                │   │
                          │ project_id        │───┘
                          │ organisation_id   │──►(filtre RLS)
                          └───────────────────┘

Règle d’or : toute table qui contient des données métier sensibles doit avoir un organisation_id. Sans ça, RLS ne peut pas s’appliquer.

Gestion des rôles : qui voit quoi ?

Dans un SaaS B2B, il y a généralement trois niveaux d’accès :

  • Utilisateur standard : voit uniquement les données de son organisation
  • Admin organisation : voit toutes les données de son organisation, peut gérer les membres
  • Super-admin SaaS (vous) : accès technique global, jamais exposé côté client

Pour implémenter cela, on utilise une fonction sécurisée qui récupère l’organisation de l’utilisateur connecté, puis on base les politiques RLS dessus :

-- Fonction sécurisée : récupère l'organisation de l'utilisateur connecté
CREATE OR REPLACE FUNCTION private.get_user_organisation_id()
RETURNS bigint
LANGUAGE sql
SECURITY DEFINER
SET search_path = private
AS $$
  SELECT organisation_id
  FROM public.members
  WHERE user_id = auth.uid()
  LIMIT 1;
$$;
REVOKE ALL ON FUNCTION private.get_user_organisation_id() FROM public;
GRANT EXECUTE ON FUNCTION private.get_user_organisation_id() TO authenticated;

Ensuite, pour chaque table métier, nous allons devoir ajouter une politique RLS. Par exemple, pour la table “members” :

-- Politique : un utilisateur voit uniquement les membres de son organisation
CREATE POLICY "Select members for authenticated"
on "public"."members"
to authenticated
FOR SELECT
USING (
  organisation_id = private.get_user_organisation_id()
);

Et voici comment gérer un usage admin :

-- Fonction pour récupérer l'organisation pour laquelle l'utilisateur est admin
CREATE OR REPLACE FUNCTION private.get_admin_organisation_id()
RETURNS bigint
LANGUAGE sql
SECURITY DEFINER
SET search_path = private
AS $$
  SELECT organisation_id
  FROM public.members
  WHERE user_id = auth.uid() and role = 'admin'
  LIMIT 1;
$$;
REVOKE ALL ON FUNCTION private.get_admin_organisation_id() FROM public;
GRANT EXECUTE ON FUNCTION private.get_admin_organisation_id() TO authenticated;

Ensuite, pour mettre à jour la table “members” avec le rôle admin :

-- Politique : Seul l'admin peut modifier un membre de son organisation
CREATE POLICY "Update members for admin authenticated"
on "public"."members"
to authenticated
FOR UPDATE
USING (
  organisation_id = private.get_admin_organisation_id()
)
WITH CHECK (
  organisation_id = private.get_admin_organisation_id()
);

Notez bien que ces fonctions ont été créées sur un schéma indépendant appelé ici private. Vous devrez bien sûr adapter ces fonctions à votre modèle de données.

Pièges courants à éviter

1. Oublier une table. RLS doit être activé sur toutes les tables métier. Une seule table sans protection suffit à créer une fuite. Tenez une checklist lors de chaque migration. Supabase vous alerte (dans les règles de sécurité) si vous n’avez pas activé RLS sur toutes les tables.

2. Les jointures qui contournent. Si une table A est protégée mais qu’elle joint une table B non protégée, les données de B peuvent fuir via la jointure. RLS s’applique table par table — pensez à l’activation de bout en bout.

3. Les migrations non testées. Ajouter une colonne, renommer une table, créer une vue — tout cela peut interagir avec les politiques existantes. Testez les accès après chaque migration en base.

4. Le super-admin dans le code. Ne codez jamais un bypass de RLS dans votre application. L’accès super-admin doit passer par des connexions techniques séparées, jamais par un utilisateur applicatif.


Partie 4 — Ce que ça change pour votre produit (et vos clients)

Sécurité by design vs. sécurité ajoutée après

Il y a deux façons de gérer la sécurité dans un produit :

  • La sécurité rajoutée : on construit vite, on sécurise plus tard. C’est tentant au démarrage. C’est risqué à long terme — les couches de sécurité s’ajoutent en patchwork sur une architecture qui n’y était pas préparée.
  • La sécurité by design : on intègre les contraintes dès la conception. Ça prend un peu plus de temps au départ. Ça évite des incidents coûteux, des refactorisations lourdes, et des clients perdus.

RLS, c’est de la sécurité by design. Elle ne se greffe pas sur une architecture existante — elle est l’architecture. La mettre en place dès le premier sprint, c’est un investissement qui rapporte à chaque nouvelle table ajoutée. En réalité, une fois que vous avez créé les quelques fonctions SQL nécessaires, cela devient un réflexe et le temps passé sur chaque nouvelle table devient négligeable.

Ce que vous pouvez dire à vos clients

Avec RLS correctement configuré, vous pouvez tenir ce discours à vos clients en toute honnêteté :

“Vos données sont protégées au niveau de la base de données elle-même. Même en cas d’erreur dans notre code, un compte client ne peut pas accéder aux données d’un autre client.”

C’est un argument de confiance fort, surtout face à des acheteurs B2B qui posent des questions sur la sécurité lors des demos ou lors de revues de conformité.

Conformité RGPD : RLS comme levier d’isolation

Le RGPD impose de garantir que les données personnelles de vos clients sont traitées et stockées de façon sécurisée. L’isolation par RLS contribue directement à deux principes clés :

  • La minimisation des accès : chaque utilisateur ne voit que ce dont il a besoin
  • La séparation des données : les données d’un client ne peuvent pas être atteintes par un autre

Ce n’est pas suffisant seul — la conformité RGPD est plus large — mais c’est une brique solide à documenter dans votre politique de sécurité et à mentionner dans vos CGV ou DPA (Data Processing Agreement).

Les limites à connaître

RLS est puissant, mais ce n’est pas une solution totale. Il ne remplace pas :

  • Le chiffrement des données sensibles (mots de passe, données bancaires, données médicales)
  • Les logs d’audit pour tracer qui a accédé à quoi et quand
  • Les sauvegardes isolées par client, utiles en cas de demande RGPD de suppression ou de portabilité
  • Les tests de pénétration réguliers pour vérifier l’ensemble de la chaîne

Une bonne architecture SaaS combine plusieurs couches. RLS en est une — essentielle, mais non exclusive.


Chez Citios, j’accompagne les fondateurs et équipes tech dans la conception d’architectures SaaS robustes : choix de stack, modèle de données, sécurité by design, audit de base existante. Que vous partiez de zéro ou que vous vouliez sécuriser un produit existant, une heure de cadrage peut éviter des mois de dette technique.