Bases de la sécurité
Cacher une clé API : la déplacer dans une Edge Function Supabase
Cacher une clé API veut dire la sortir du navigateur, et une Edge Function Supabase est le plus petit endroit où la mettre. Deux étapes comptent davantage.

En bref
- Rien dans un navigateur ne sait garder un secret, donc cacher une clé API veut dire la déplacer là où c'est possible. Une Edge Function Supabase est le plus petit serveur que tu puisses avoir.
- Le déplacement tient en quatre étapes. Remplace d'abord la clé que tu as déjà publiée, parce que la copie présente dans ton ancien bundle reste lisible après que tu l'as retirée.
- Une function déployée exige un jeton par défaut, et la clé publique de ton propre frontend satisfait cette exigence. Vérifier qui appelle est une étape à part, et c'est elle qui empêche un inconnu de dépenser ton quota.
Tous les guides sur une clé API qui a fuité finissent au même endroit : déplace-la vers un serveur. Puis ils s'arrêtent. Il te reste une app construite dans Lovable, Bolt ou Cursor, aucun serveur à l'horizon, et une phrase qui suppose que tu sais déjà quoi faire ensuite.
Voici ce que guide après guide laisse de côté. Déplacer la clé dans une Edge Function Supabase la sort de ta page, et à soi seul cela n'empêche pas un inconnu de l'utiliser. La documentation de Supabase dit pourquoi : le contrôle qu'une function déployée exécute par défaut accepte aussi ta clé publique, et ta clé publique est dans ton frontend, à la portée de qui veut la copier. Cacher la clé est la moitié facile. L'autre moitié, celle dont personne ne parle, c'est de décider à qui ta nouvelle function répond.
Vois la clé comme le passe-partout d'une réserve. Pour l'instant il est scotché à l'intérieur de la vitrine, où le lit quiconque s'arrête. Une Edge Function est une arrière-boutique avec un guichet : la clé va là-dedans, et les visiteurs demandent au guichet au lieu d'entrer se servir. Que ce soit un progrès dépend de qui le guichet laisse passer.
Où mettre ma clé API, si ce n'est pas dans le frontend ?
Partout sauf dans le navigateur. Une Edge Function est la plus petite version de cela.
Un navigateur ne sait pas garder un secret. Tout ce dont ta page a besoin pour
fonctionner est téléchargé par chaque visiteur, et un visiteur peut tout lire :
le code, les images, les valeurs compilées dans le code. Ce n'est pas un défaut
de ton outil. C'est ce qu'est une page web.
Quelles clés API sont sûres dans ton frontend
est la version longue ; la courte, c'est qu'une clé sk_, une clé OpenAI ou une
clé secrète Supabase n'allait jamais survivre à une livraison dans un navigateur.
Une Edge Function Supabase est un petit bout de code qui tourne sur les machines de Supabase. Elle peut lire les secrets que tu as définis sur ton projet, elle répond à une adresse web qui lui est propre, et ton app l'appelle par son nom. Tu écris un fichier, Supabase l'exécute, et il n'y a aucun serveur à louer, à corriger ou à maintenir éveillé.
Remplace la clé déjà publiée, avant de déplacer quoi que ce soit
La clé qui est dans ton frontend aujourd'hui est déjà publique, et elle le reste après que tu l'as retirée du code.
Chaque visiteur qui a chargé ton site l'a téléchargée. Chaque robot aussi, et il en existe d'automatiques qui parcourent tout le web à la recherche exactement de ces chaînes. Ton ancien bundle est par ailleurs encore dans ton historique de versions et dans tous les caches qui ont une copie de ta page. Supprimer une ligne d'un fichier qui t'appartient ne change rien à une valeur déjà distribuée.
L'ordre est donc : créer une nouvelle clé chez le fournisseur, la garder un moment, et révoquer l'ancienne une fois la function ci-dessous en service. Lis ensuite ta page de facturation et tes journaux d'usage pour toute la période où l'ancienne clé était dehors. Un remplacement arrête ce qui vient ; il n'a aucun effet sur ce qui a déjà eu lieu. Si la clé était une clé secrète Supabase, l'ordre dans lequel travailler a une subtilité à connaître avant.
Les quatre étapes
Enregistrer le secret, écrire la function, la déployer, puis changer ton app pour qu'elle appelle la function au lieu du fournisseur.
| Étape | En ligne de commande | Dans le tableau de bord |
|---|---|---|
| 1. Enregistrer le secret | supabase secrets set MY_API_KEY=… | Edge Functions → Secrets, puis Key et Value |
| 2. Écrire la function | supabase functions new forward-request | Edge Functions → Deploy a new function → Via Editor |
| 3. La déployer | supabase functions deploy | Le bouton Deploy sous l'éditeur |
| 4. L'appeler depuis ton app | supabase.functions.invoke('…') | Pareil, dans le code de ton app |
Dans la function le secret arrive comme variable d'environnement, c'est-à-dire une valeur nommée que le code peut lire et personne de l'extérieur. La doc Supabase sur les secrets des Edge Functions donne la référence complète, et trois détails dedans épargnent une heure de confusion :
- Un nom de secret ne peut pas commencer par
SUPABASE_. Ce préfixe est réservé aux valeurs que Supabase définit pour toi, et le tableau de bord comme l'API le refusent. - Un nouveau secret est lisible tout de suite. Tu ne redéploies pas la function après en avoir changé un.
- Local et production sont séparés. La pile locale lit
supabase/functions/.env, qui n'est pas le même endroit que les secrets de ton projet en service. Définis donc la valeur aux deux endroits, sinon la function marche sur ta machine et échoue une fois déployée.
Supprime ensuite l'ancienne variable de ton frontend. Si elle s'appelait VITE_,
NEXT_PUBLIC_ ou EXPO_PUBLIC_, ce préfixe était l'instruction de compiler la
valeur dans le bundle, et le préfixe est toute
l'histoire de son arrivée là.
Une exigence de jeton veut-elle dire que seuls mes utilisateurs appellent ?
Non, et c'est le passage de cet article qui mérite deux lectures.
Supabase active par défaut un contrôle nommé verify_jwt. Il inspecte l'en-tête
Authorization avant que ton code tourne et refuse la requête si rien de valide
ne s'y trouve. Cela sonne comme une porte que seuls tes utilisateurs ouvrent. Ce
n'en est pas une, car Supabase documente aussi que le même contrôle
accepte une clé publique ou secrète
sur l'un ou l'autre en-tête, par compatibilité avec les projets plus anciens, et
ajoute clairement que le contrôle seul n'authentifie pas un appelant qui envoie
uniquement une clé API.
Ta clé publique est dans ton frontend. C'est correct et c'est fait pour. Cela veut dire aussi que quiconque ouvre ton site, lit la clé et l'envoie à ta nouvelle function passe le contrôle de la plateforme et entre dans ton code.
Dans la réserve, verify_jwt est un gardien qui vérifie que tu portes un badge
visiteur. Chaque visiteur en a un, puisque tu les distribues à l'entrée. La
personne au guichet fait un autre métier, et c'est elle qui demande quel visiteur
tu es.
Verrouiller la function, pour ne pas avoir bâti un proxy ouvert
Décide quels appelants ta function accepte, et écris cette décision dans la function.
Supabase livre un wrapper pour cela, donc c'est une ligne et pas un projet. Mets
auth: 'user' et la function accepte le jeton d'un utilisateur connecté et donne
à ton code un client de base de données déjà limité aux règles de Row Level
Security de cet utilisateur :
import { withSupabase } from 'npm:@supabase/server@1'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
// ctx.userClaims dit qui appelle. Refuse ici ce que tu ne veux pas,
// puis appelle le fournisseur avec le secret pris dans l'environnement.
return Response.json({ ok: true })
}),
}
La page Securing Edge
Functions liste les autres
modes, et deux d'entre eux comptent pour une app ordinaire. Une function appelée
par une autre machine plutôt que par un navigateur prend auth: 'secret', avec
la clé secrète envoyée dans l'en-tête apikey. Une function qui reçoit des
webhooks de Stripe ou de GitHub ne peut prendre ni l'un ni l'autre, car ces
fournisseurs n'ont aucun jeton à toi ; elle met verify_jwt = false et vérifie
la signature du fournisseur dans le handler.
Quel que soit ton choix, décide délibérément de ce qui arrive quand un appelant imprévu se présente. L'exemple que donne Supabase d'une function qui peut accepter tout le monde est un contrôle de santé, et un contrôle de santé ne coûte rien quand un inconnu l'appelle. Une function qui relaie un appel d'API payant te facture chacun d'eux.
Puis-je utiliser service_role dans une Edge Function ?
Oui. Une function est le seul endroit où une clé secrète Supabase a sa place.
Tu n'as pas non plus à la coller. Supabase met les clés du projet dans
l'environnement de la function, donc le code les y lit plutôt que dans un secret
que tu aurais défini. Les projets récents reçoivent SUPABASE_SECRET_KEYS et
SUPABASE_PUBLISHABLE_KEYS, chacun un petit répertoire de clés nommées ; les
projets plus anciens reçoivent SUPABASE_SERVICE_ROLE_KEY et
SUPABASE_ANON_KEY sous leurs noms d'origine. Ce qui a changé quand Supabase a
renommé ses clés dit laquelle des deux paires tu as.
Utilise la clé secrète pour le travail qui doit vraiment voir chaque ligne, par exemple écrire une trace que l'utilisateur ne doit pas pouvoir modifier. Pour tout ce qu'un utilisateur lit à son propre sujet, utilise son jeton et laisse tes règles de Row Level Security filtrer, puisque c'est leur rôle.
Vérifier depuis le navigateur, la seule preuve qui compte
Charge ton site en service, ouvre l'onglet réseau, et regarde ce que ton navigateur envoie vraiment.
Les deux choses à vérifier :
- Aucune requête ne porte la clé. Parcours la partie de ton app qui appelait
le fournisseur. La requête sortante doit aller vers
…supabase.co/functions/v1/ta-function, et le seul justificatif dedans doit être ta clé publique ou le jeton de ton utilisateur. - La clé n'est pas dans le code téléchargé. Utilise la recherche de ton navigateur sur tous les fichiers chargés et colle la première douzaine de caractères de l'ancienne clé. Aucun résultat est la réponse que tu veux.
Seuls une reconstruction et un redéploiement retirent une valeur du bundle. Une clé qui apparaît encore dans la recherche veut donc souvent dire que le frontend est parti avant que la variable n'en sorte.
Notre scan gratuit fait le second contrôle depuis l'extérieur et nomme ce qu'il arrive à lire dans ton bundle en service, y compris quelle clé Supabase tu as livrée. Il prend environ 20 secondes et ne demande aucun compte : scanne ton app.
Faire cela depuis Lovable, Bolt ou Replit
Passe par le tableau de bord Supabase. Il n'y a aucune étape en terminal.
Ouvre ton projet, choisis Edge Functions dans la barre latérale, puis Deploy a new function → Via Editor. Le guide de démarrage du tableau de bord de Supabase déroule tout avec des captures d'écran, et parmi les modèles proposés il y en a un pour relayer un fournisseur d'IA, c'est-à-dire exactement la forme de ce travail. Le déploiement prend entre dix et trente secondes, et la function est ensuite en service à une adresse à elle. Ton secret se met sur la page Edge Function Secrets, dans la même section.
Une réserve à connaître avant de t'y fier : Supabase écrit que l'éditeur du tableau de bord n'a ni gestion de versions, ni versions, ni retour arrière, et le recommande pour du travail rapide plutôt que pour du code destiné à durer. Appuyer sur Deploy écrase ce qui était là. Si la function finit par faire quelque chose qui compte pour toi, télécharge-la depuis cette page et garde le fichier.
Il existe aussi une version Replit de cette question, parce que Replit a son propre magasin de secrets et que ton app y fait tourner son propre serveur. Ce que font vraiment les Replit Secrets est cette version.
Quand une Edge Function est la mauvaise réponse
Deux cas, et dans les deux le déplacement te coûte du travail et n'apporte rien.
La clé était publique par conception. Une clé Stripe pk_live_, une clé
publique Supabase ou une clé anon, un jeton public Mapbox : celles-là sont
faites pour vivre dans un navigateur, et la protection est ailleurs. En mettre
une derrière une function ajoute un détour et ne retire aucun risque.
Quelles clés ce sont en donne
la liste.
La clé peut être restreinte à ton propre site. Les clés navigateur de Google sont l'exemple courant : dans la console Google tu peux en limiter une à tes domaines, et c'est précisément la solution que Google prévoit pour ce cas. La clé reste lisible et cesse de servir à quiconque la copie.
Tout le reste a sa place sur un serveur. Une clé OpenAI ou Anthropic n'a pas de variante publique, et c'est pourquoi une clé de fournisseur d'IA dans ton frontend n'a aucun réglage qui la sauve, et aucune minification ne cache une clé secrète Stripe à qui lit ta page.
Ce que tu peux faire maintenant
Que faire
- Remplace la clé d'abord. Crée-en une nouvelle chez le fournisseur, mets-la dans le secret de la function, et révoque l'ancienne une fois la function en service.
- Enregistre le secret sur ton projet Supabase, pas dans ton dépôt et pas dans une variable
VITE_. Définis-le séparément pour le développement local et pour la production. - Déploie une function qui utilise le secret, et change ton app pour qu'elle appelle la function par son nom au lieu du fournisseur.
- Ajoute un contrôle sur qui appelle.
verify_jwtseul accepte la clé publique de ton propre frontend, donc il ne tient pas un inconnu à l'écart. - Supprime l'ancienne variable, reconstruis, et confirme dans l'onglet réseau de ton navigateur que rien de sortant ne porte la clé.
- Relis ta facturation et ton usage pour toute la période où l'ancienne clé était publique. Un remplacement arrête le prochain débit et pas le dernier.
Si tu préfères passer en revue toute ton app plutôt qu'une seule clé, la checklist de sécurité en 10 minutes couvre ceci avec les autres choses à fermer dans une app fraîchement lancée.
FAQ
Où mettre ma clé API, si ce n'est pas dans le frontend ?
Partout sauf dans le navigateur. Une Edge Function Supabase est la plus petite option : tu enregistres la clé comme secret sur ton projet, tu écris un petit fichier qui l'utilise, et ton app appelle cette function par son nom plutôt que d'appeler le fournisseur directement. Une route serverless chez ton hébergeur ou un serveur à toi fonctionnent pareil. Leur point commun, c'est que la clé est lue là où ton visiteur ne la voit pas.
Qu'est-ce qu'une Edge Function Supabase ?
Un petit bout de code qui tourne sur les machines de Supabase plutôt que dans le navigateur de ton visiteur. Il répond à une adresse web qui lui est propre, il peut lire les secrets que tu as définis sur ton projet, et ton app l'appelle avec supabase.functions.invoke. Tu écris un fichier, Supabase l'exécute, et il n'y a aucun serveur à louer ni à entretenir.
Dois-je remplacer ma clé après le déplacement ?
Oui, et en premier. La clé qui était dans ton frontend a été téléchargée par chaque visiteur et par chaque robot qui a lu ton site, et la retirer du code ne change rien à ces copies. Crée une nouvelle clé chez le fournisseur, mets-la dans le secret de ton Edge Function, et révoque l'ancienne. Relis ensuite ta facturation et ton usage pour toute la période où l'ancienne clé était valide.
N'importe qui peut-il appeler mon Edge Function ?
Par défaut une function refuse une requête sans aucun jeton, mais ce contrôle est plus faible qu'il n'en a l'air. Supabase documente que le contrôle de la plateforme accepte aussi ta clé publique ou secrète, et ta clé publique est dans ton frontend, où tout le monde peut la lire. Le contrôle arrête donc une requête vide et pas une requête décidée. Pour n'accepter que tes utilisateurs connectés, vérifie l'appelant à l'intérieur de la function.
Puis-je utiliser service_role dans une Edge Function ?
Oui. Une Edge Function est le seul endroit où une clé secrète Supabase a sa place, et Supabase met les clés du projet dans l'environnement de la function pour toi, donc tu n'en colles jamais une. Utilise-la pour le travail qui doit voir chaque ligne, et utilise le jeton de l'appelant pour tout ce qui doit respecter tes règles de Row Level Security.
Puis-je faire cela sans terminal ?
Oui. Le tableau de bord Supabase a une section Edge Functions où tu écris une function dans le navigateur, tu appuies sur Deploy, et tu définis ton secret sur la page Edge Function Secrets. Supabase précise que l'éditeur du tableau de bord ne garde aucun historique de versions, et le conseille donc pour du travail rapide, la ligne de commande servant pour ce que tu comptes garder.