Clé anon vs service_role Supabase : laquelle exposer ?
Réponse en deux phrases. La clé anon (bientôt la publishable key) peut rester dans ton frontend : elle n'ouvre que ce que Row Level Security (RLS) autorise. La clé service_role (bientôt la secret key) contourne RLS et ne doit jamais quitter ton serveur — si elle fuite dans le navigateur, toute ta base devient lisible et modifiable par n'importe qui.

Sommaire
- anon et service_role : c'est quoi, concrètement ?
- Laquelle peut rester dans le frontend ?
- Pourquoi la service_role ne doit jamais toucher un navigateur
- Ce qui change fin 2026 : publishable et secret keys
- Où la service_role (et la secret key) doit vivre
- Comment vérifier si tu as fuité une clé
- Questions fréquentes
- La clé anon est-elle publique ?
- Je vois une clé qui commence par eyJ, c'est grave ?
- Puis-je utiliser la service_role pour débloquer le dev en local ?
- Comment faire tourner (rotater) une clé qui a fuité ?
- La nouvelle secret key peut-elle aller dans le frontend ?
- Sources
C'est la question qui revient dès qu'on branche une app Lovable, Bolt ou Cursor sur Supabase. Ton agent de code t'a généré un createClient avec une clé, tu ne sais pas laquelle est laquelle, et tu te demandes si c'est grave de la voir dans le JavaScript livré au navigateur. La réponse dépend de la clé. Voici comment les distinguer, ce que chacune autorise, et où la service_role ne doit jamais apparaître.

anon et service_role : c'est quoi, concrètement ?
Chaque requête envoyée à ton projet Supabase porte une clé API. Cette clé ne dit pas qui tu es : elle dit quoi fait l'appel — une page web, une app mobile, un serveur ou une Edge Function. L'identification de l'utilisateur, elle, est gérée à part par Supabase Auth, quand quelqu'un se connecte.
Concrètement, Supabase fournit quatre types de clés :
| Type | Format | Privilèges | Où l'utiliser |
|---|---|---|---|
| Publishable key | sb_publishable_... |
Faibles | Navigateur, app mobile, CLI, code source |
| Secret key | sb_secret_... |
Élevés | Serveur, Edge Function uniquement |
| anon (legacy) | JWT long (eyJ...) |
Faibles | Ancienne version de la publishable |
| service_role (legacy) | JWT long (eyJ...) |
Élevés | Ancienne version de la secret |
Le point clé : chaque clé correspond à un rôle Postgres. La clé anon/publishable, quand personne n'est connecté, s'exécute avec le rôle anon ; quand un utilisateur est connecté, avec le rôle authenticated. La clé service_role/secret s'exécute, elle, avec le rôle service_role, qui porte l'attribut BYPASSRLS.
Ce mot BYPASSRLS change tout. C'est lui qui fait la différence entre « sûr dans le navigateur » et « jamais dans le navigateur ».
Laquelle peut rester dans le frontend ?
La clé anon (publishable) peut rester dans le frontend. C'est par design : elle est faite pour être lue par n'importe qui, parce qu'elle ne donne accès qu'à ce que tes policies RLS autorisent explicitement.
L'image à retenir, c'est celle de la documentation Supabase : pense aux données de ton projet comme à un immeuble. La clé publishable est scotchée sur la porte d'entrée — tout le monde peut la lire, et elle n'ouvre que le hall. La clé secret, elle, est la clé maîtresse : elle reste dans ta poche.
Ça veut dire une chose importante : exposer la clé anon n'est pas une faille en soi. La sécurité ne repose pas sur le secret de cette clé, mais sur tes policies RLS. Si ton RLS est bien configuré, un inconnu qui récupère ta clé anon depuis le JavaScript ne voit que les données que tu as décidé de rendre publiques. Si ton RLS est absent ou mal réglé, c'est là que ça casse — et c'est un sujet à part, couvert dans notre guide pour sécuriser une app Supabase avec RLS.

Pourquoi la service_role ne doit jamais toucher un navigateur
La clé service_role (et la nouvelle secret key) contourne toutes tes policies RLS. Elle s'exécute avec le rôle service_role, qui porte BYPASSRLS : aucune règle que tu écris ne s'applique à elle.
Conséquence : une clé service_role qui fuit dans le frontend, c'est un accès total à ta base. Lecture, écriture, suppression — sans condition. N'importe qui qui ouvre les DevTools de ton site peut la copier, puis lire les données de tous tes utilisateurs, les modifier, ou vider des tables.
Ce n'est pas de la dramatisation : la documentation Supabase l'écrit noir sur blanc — « A leaked secret key exposes all of your project's data » (une clé secret qui fuit expose toutes les données de ton projet). Et elle rappelle la règle : jamais dans un navigateur, jamais dans une app livrée, jamais dans le contrôle de source.
Les endroits où elle ne doit jamais apparaître :
- dans un fichier JavaScript ou un bundle livré au navigateur ;
- dans une variable préfixée
NEXT_PUBLIC_(Next.js) ouVITE_(Vite), injectées dans le client ; - dans un dépôt Git public ;
- dans une réponse d'API ou une page HTML.
Ce qui change fin 2026 : publishable et secret keys
Une évolution à connaître : Supabase déprécie les clés anon et service_role d'ici fin 2026, au profit des clés publishable (sb_publishable_...) et secret (sb_secret_...). La source primaire est la page API keys des docs Supabase.
Trois différences à retenir :
- Le format. Les publishable et secret keys sont des chaînes courtes, pas des JWT. Si un tuto, un outil ou ton agent de code te dit de copier une longue clé qui commence par
eyJ, il a été écrit pour l'ancien système. - Le sens reste le même. Publishable = faible privilège, peut être publique. Secret = privilèges élevés, contourne RLS, côté serveur uniquement. Rien ne change sur le fond.
- Les anciennes clés restent actives. Créer une publishable ou une secret ne révoque pas tes clés legacy. Les deux systèmes fonctionnent en parallèle, et tes clés anon/service_role restent valides jusqu'à ce que tu les désactives dans Settings > API Keys du dashboard.
Autre avantage des nouvelles clés : la secret key est conçue pour ne pas fonctionner depuis un navigateur, avec des vérifications côté Supabase pour empêcher son mauvais usage. La service_role (JWT) n'a pas ce garde-fou.
Où la service_role (et la secret key) doit vivre
La règle est simple : côté serveur uniquement. Concrètement, dans les composants que tu contrôles — une Edge Function, un serveur, un job planifié, un back-office qui fait ses propres vérifications d'autorisation.
Pour une app Supabase avec des Edge Functions, les secrets se stockent ainsi :
- En local, dans
supabase/functions/.env, que ton runtime charge au démarrage de la stack. - En production, via
supabase secrets setou le dashboard (Edge Function Secrets).
Et le fichier .env va dans ton .gitignore. Un .env commité dans Git, c'est tous tes secrets livrés à quiconque peut lire le dépôt.
À l'intérieur d'une Edge Function, Supabase injecte déjà les bonnes clés. Tu crées deux clients :
import { createClient } from 'npm:@supabase/supabase-js@2'
const PUBLISHABLE = JSON.parse(Deno.env.get('SUPABASE_PUBLISHABLE_KEYS')!)
const SECRET = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
// Opérations côté utilisateur : respecte Row Level Security
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, PUBLISHABLE['default'])
// Opérations admin : contourne Row Level Security
const supabaseAdmin = createClient(Deno.env.get('SUPABASE_URL')!, SECRET['default'])
Le client supabase (publishable) pour ce que fait l'utilisateur, le client supabaseAdmin (secret) uniquement pour les tâches admin côté serveur.

Comment vérifier si tu as fuité une clé
Le réflexe : regarde ton propre site comme le ferait un inconnu. Ouvre les DevTools, va dans l'onglet Network ou Sources, et cherche tes clés. Cherche en particulier les chaînes qui commencent par eyJ (un JWT service_role legacy) ou sb_secret_ (une secret key). Si tu en trouves une dans le JavaScript livré, c'est une fuite à traiter tout de suite.
Regarde aussi tes source maps si elles sont en ligne : elles exposent ton code source d'origine, et donc les clés que tu aurais pu y laisser.
Enfin, fouille ton historique Git. Une clé commitée puis supprimée reste dans l'historique tant que tu ne l'as pas révoquée. La suppression du fichier ne suffit pas : il faut révoquer la clé.
Voici un prompt à coller dans ton agent de code pour qu'il vérifie lui-même :
Audite ce projet pour détecter toute clé Supabase exposée côté client.
1. Cherche dans tout le code livré au navigateur (composants, bundles, variables NEXT_PUBLIC_*, VITE_*, .env importé côté client) toute clé Supabase.
2. Signale immédiatement : une chaîne qui commence par eyJ (JWT anon ou service_role legacy) ou par sb_secret_ (secret key). C'est une fuite critique.
3. Pour la clé anon/publishable (sb_publishable_...), ce n'est pas une fuite : vérifie simplement que des policies RLS couvrent chaque table.
4. Déplace toute service_role/secret key vers le serveur (Edge Function, variable d'environnement serveur, supabase secrets set), et ajoute les fichiers .env au .gitignore.
5. Vérifie ensuite que plus aucune secret/service_role n'apparaît dans le build client, et que chaque table a une policy RLS.
Si tu as fuité une clé : révoque-la d'abord, puis corrige le code, puis régénère une nouvelle clé. L'ordre compte — corriger le code sans révoquer laisse l'ancienne clé valide entre les mains de celui qui l'a copiée.
Pour ne pas passer l'après-midi à chercher à la main, tu peux lancer un scan gratuit de ton site sur Rustine : il regarde ton app comme le ferait un inconnu, montre la clé qui fuit avec la requête qui le prouve, et te donne le correctif à coller.
Questions fréquentes
La clé anon est-elle publique ?
Oui, et c'est normal. La clé anon (publishable) est conçue pour être lue par n'importe qui. Sa sécurité repose entièrement sur Row Level Security : elle n'ouvre que ce que tes policies autorisent. Ce n'est pas un secret à protéger, c'est un badge d'accès au hall.
Je vois une clé qui commence par eyJ, c'est grave ?
eyJ est le début d'un JWT. Ça peut être une clé anon (pas grave) ou une clé service_role (critique). La différence se joue sur le rôle : une service_role porte l'attribut BYPASSRLS et contourne tout. Si une clé eyJ est dans ton frontend, vérifie de quel type elle est — et dans le doute, traite-la comme une fuite.
Puis-je utiliser la service_role pour débloquer le dev en local ?
Tu peux, mais garde-la hors du navigateur même en local. Une erreur classique : utiliser la service_role dans le client pour « faire marcher plus vite » une requête que RLS bloquait. C'est la mauvaise correction — ça marche en local, puis ça part en prod avec un accès total exposé. La bonne correction, c'est d'écrire la policy RLS qui manque.
Comment faire tourner (rotater) une clé qui a fuité ?
Dans le dashboard Supabase, Settings > API Keys : révoque la clé compromise, puis génère une nouvelle clé. Mets à jour ton code avec la nouvelle valeur. Tant que tu n'as pas révoqué l'ancienne, elle reste valide même si tu ne l'utilises plus.
La nouvelle secret key peut-elle aller dans le frontend ?
Non. Même raison que la service_role : elle contourne RLS et donne un accès total. Sa différence, c'est qu'elle est mieux protégée contre le mauvais usage (elle ne fonctionne pas depuis un navigateur), mais la règle ne change pas : côté serveur uniquement.