Comment sécuriser votre app Supabase en 10 minutes
Supabase est le backend de référence pour les vibe coders. Lovable, Bolt et des dizaines d'outils de coding IA utilisent Supabase par défaut pour l'authentification, la base de données et le stockage. C'est rapide à mettre en place, généreux sur le plan gratuit, et parfaitement compatible avec des frameworks comme Next.js et React.
Mais il y a un problème : la plupart des apps Supabase sont déployées avec des erreurs de configuration de sécurité critiques. Nous avons scanné des centaines d'applications en production avec ScanVibe, et les mêmes problèmes reviennent sans cesse.
La bonne nouvelle ? Vous pouvez tous les corriger en environ 10 minutes. Voici comment.
1. Activez Row Level Security (RLS) sur chaque table
C'est le problème de sécurité Supabase numéro 1 que nous constatons. Sans RLS, n'importe qui possédant votre anon key peut lire et écrire chaque ligne de votre base de données.
Le problème
Lorsque vous créez une table dans Supabase, RLS est désactivé par défaut. Cela signifie que votre API key anon — qui est intégrée dans votre JavaScript frontend — donne un accès complet à toutes les données.
-- Check which tables have RLS disabled
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
La solution
Activez RLS sur chaque table, puis ajoutez des policies pour définir ce qui doit être accessible :
-- Enable RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Allow users to read their own profile
CREATE POLICY "Users can read own profile"
ON public.profiles FOR SELECT
USING (auth.uid() = id);
-- Allow users to update their own profile
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE
USING (auth.uid() = id);
Patterns courants
Pour les tables qui doivent être lisibles publiquement mais modifiables uniquement par leurs propriétaires :
-- Anyone can read
CREATE POLICY "Public read" ON public.posts
FOR SELECT USING (true);
-- Only the author can insert/update/delete
CREATE POLICY "Author can modify" ON public.posts
FOR ALL USING (auth.uid() = author_id);
Pour les tables privées (ex. : paramètres utilisateur, informations de paiement) :
-- Only authenticated users can access their own rows
CREATE POLICY "Private access" ON public.user_settings
FOR ALL USING (auth.uid() = user_id);
2. Arrêtez d'exposer votre clé service_role
Votre projet Supabase possède deux clés :
Le problème
Nous trouvons fréquemment la clé service_role dans :
- Les bundles JavaScript frontend
- Les fichiers
.envcommités dans des dépôts publics - L'initialisation du client Supabase côté client
La solution
- Vérifiez vos variables d'environnement. La clé service_role ne doit se trouver que dans les variables d'environnement côté serveur (pas de préfixe
NEXT_PUBLIC_dans Next.js).
# WRONG — exposed to the browser
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhb...
# RIGHT — server-side only
SUPABASE_SERVICE_ROLE_KEY=eyJhb...
- Utilisez la clé anon dans votre client frontend :
// Frontend client — uses anon key
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
- Utilisez la clé service_role uniquement dans le code côté serveur (API routes, server actions) :
// Server-side only — never import this in client code
import { createClient } from '@supabase/supabase-js';
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
3. Sécurisez vos buckets de stockage
Le problème
De nombreuses applications créent des buckets publics ou ont des storage policies qui permettent à n'importe qui de télécharger ou d'écraser des fichiers. Nous avons constaté :
- Des buckets où tout utilisateur authentifié peut lire tous les fichiers
- Des policies d'upload sans restriction de taille ou de type de fichier
- Aucune policy (ce qui signifie aucun accès — ou un accès total si le bucket est public)
La solution
-- Only allow users to access their own files
CREATE POLICY "Users access own files" ON storage.objects
FOR ALL USING (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
• Utilisez des dossiers spécifiques par utilisateur :
avatars/{user_id}/photo.jpg• Définissez des limites de taille de fichier dans votre logique d'upload
• Validez les types de fichiers côté serveur
• Utilisez des signed URLs pour les fichiers privés au lieu de rendre les buckets publics
4. Configurez correctement l'authentification
Supabase Auth est solide par défaut, mais il existe des erreurs de configuration courantes.
Désactivez les confirmations par email avec précaution
Si vous désactivez les confirmations par email (courant en phase de développement), pensez à les réactiver avant le passage en production. Sans confirmation, n'importe qui peut créer des comptes avec de fausses adresses email.
Restreignez les URLs de redirection
Dans votre tableau de bord Supabase sous Authentication > URL Configuration :
# Add only your production domain and localhost
https://yourdomain.com/**
http://localhost:3000/**
https://* — cela permet des attaques par redirection où un attaquant peut voler des tokens d'authentification en redirigeant vers son propre domaine.Activez le rate limiting
Supabase dispose d'un rate limiting intégré pour les endpoints d'authentification. Assurez-vous qu'il est activé :
- Allez dans Authentication > Rate Limits
- Définissez des limites raisonnables (ex. : 10 inscriptions par heure par IP)
5. Auditez vos fonctions de base de données
Si vous utilisez des Edge Functions ou des fonctions de base de données Supabase, elles peuvent contourner RLS si elles utilisent SECURITY DEFINER.
Le problème
-- This function runs with the creator's permissions, not the caller's
CREATE FUNCTION get_all_users()
RETURNS SETOF public.profiles
LANGUAGE sql
SECURITY DEFINER -- Dangerous: bypasses RLS
AS $$
SELECT * FROM public.profiles;
$$;
La solution
Utilisez SECURITY INVOKER sauf si vous avez une raison spécifique de ne pas le faire :
CREATE FUNCTION get_user_profile(user_id uuid)
RETURNS public.profiles
LANGUAGE sql
SECURITY INVOKER -- Safe: respects RLS
AS $$
SELECT * FROM public.profiles WHERE id = user_id;
$$;
Si vous devez utiliser SECURITY DEFINER, ajoutez des vérifications de permissions explicites à l'intérieur de la fonction.
Checklist rapide
Parcourez cette liste en 10 minutes :
- RLS activé sur toutes les tables du schéma public
- Policies RLS définies pour chaque table (pas de "allow all" sauf si c'est intentionnel)
- La clé
service_rolen'est dans AUCUNE variable d'environnementNEXT_PUBLIC_ni dans le code frontend - Les fichiers
.envsont dans le.gitignore - Les buckets de stockage ont des policies appropriées
- Les URLs de redirection d'authentification sont restreintes à vos domaines
- La confirmation par email est activée en production
- Les fonctions de base de données utilisent
SECURITY INVOKERpar défaut - Le rate limiting est configuré pour les endpoints d'authentification
Automatisez tout avec ScanVibe
Vous ne voulez pas vérifier tout cela manuellement à chaque déploiement ? ScanVibe scanne votre application Supabase et détecte automatiquement tous ces problèmes. Nous vérifions :
Un scan. 30 secondes. Tous les problèmes détectés.