Bases de la sécurité
"No API key found in request" sur Supabase, et le mauvais correctif
"No API key found in request" veut dire que votre requête est arrivée sans clé. La plupart des réponses pointent vers les règles de votre base.

En bref
- "No API key found in request" veut dire que votre requête est arrivée chez Supabase sans en-tête apikey, et qu'elle a été refusée à la porte avant que votre base de données soit interrogée.
- Le bon correctif est votre clé publiable dans cet en-tête, celle qui est faite pour être publique. La bibliothèque cliente de Supabase l'envoie pour vous à chaque requête.
- Les deux correctifs vers lesquels on se tourne à la place sont la clé secrète et une règle plus souple sur la table. Les deux font taire le message, et les deux laissent vos lignes lisibles par quiconque détient la clé livrée dans votre application.
Hier votre application fonctionnait. Aujourd'hui une liste qui se remplissait de lignes reste vide, et si vous ouvrez la console du navigateur il y a une courte réponse de Supabase là où devraient être les données :
{
"message": "No API key found in request",
"hint": "No `apikey` request header or url param was found."
}
C'est tout ce que vous obtenez. Rien sur la table que vous lisiez, sur ce que vous avez envoyé, ni sur ce qu'il faut changer.
Voici la partie que guide après guide se trompe : "No API key found in request" parle d'un en-tête manquant, et la plupart des conseils que vous trouverez parlent des règles de votre base de données. Sur le forum de discussion de Supabase ce message a son propre fil, seize personnes y proposent huit remèdes différents, et trois d'entre eux disent d'ajouter ou d'assouplir une règle sur l'une de vos tables. Tous ceux qui ont essayé racontent que le message a cessé. Savoir si une règle a jamais été la cause est une autre question, et le fil ne la tranche jamais. Ce qui est certain ensuite, c'est que plus de monde peut lire la table qu'avant.
Ce que veut dire "No API key found in request"
Supabase a refusé votre requête à la porte d'entrée, avant que votre base de données soit interrogée.
Chaque requête vers votre projet arrive d'abord à une porte que Supabase place
devant tout le reste. Son rôle à cet instant est étroit : lire l'en-tête
apikey, comparer la valeur aux clés de votre projet, et laisser passer la
requête. Sans clé à lire, elle s'arrête là et répond 401, le code de statut qui
veut dire « je ne sais pas qui vous êtes ». La documentation de Supabase sur
cette porte dit elle-même qu'une clé absente ou invalide est refusée ainsi.
Voyez cette porte comme un gardien à l'entrée de l'immeuble, et non comme une serrure sur un classeur. Le gardien demande un badge à tous ceux qui arrivent. Les règles qui décident qui peut ouvrir quel classeur sont deux étages plus haut, et ici personne ne les a consultées, parce que rien n'a dépassé l'entrée.
Pour une erreur, celle-ci est donc douce. Rien n'a été lu, rien n'a été écrit, et vos données sont exactement comme elles étaient. Une requête a été refusée.
Pourquoi ma requête est-elle arrivée sans clé ?
Parce que la valeur que votre application devait envoyer ne figurait pas dans la requête que votre navigateur a réellement faite. Quatre causes couvrent presque tous les cas.
La clé n'est jamais arrivée dans l'application construite. Votre code la lit
dans une variable d'environnement, la construction qui a produit votre site en
ligne n'avait pas cette variable, et createClient a reçu une chaîne vide. Tout
compile, l'application se charge, et chaque requête part sans clé. Dans un projet
Vite ou Next, la variable doit en plus porter le préfixe qui la déclare sûre pour
le navigateur, ce qui est
un piège à part entière.
Vous appelez le chemin REST à la main. Un fetch écrit vers
/rest/v1/votre_table envoie les en-têtes que vous avez tapés, et rien d'autre.
La bibliothèque cliente de Supabase ajoute apikey pour vous ; une requête
écrite à la main n'a que ce que vous lui avez donné.
Quelque chose entre les deux l'a supprimé. Une règle de réécriture, un proxy ou votre propre passerelle se trouve entre votre application et Supabase et transmet la requête sans l'en-tête.
Vous regardez une redirection et non une requête de données. Si le message
apparaît après qu'une personne se connecte ou clique sur un lien de confirmation,
et que la barre d'adresse affiche encore votre URL Supabase, le réglage à
regarder est la Site URL sous Auth. La réponse la plus réagie de tout ce fil
Supabase vient de quelqu'un qui y avait écrit l'adresse de son site sans le
https:// devant.
Le correctif, en une ligne
Envoyez votre clé publiable dans l'en-tête apikey.
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
'https://votre-projet.supabase.co',
'sb_publishable_…',
)
Créez le client ainsi et la bibliothèque place la clé dans le bon en-tête à
chaque requête, si bien que vous ne tapez jamais cet en-tête. La référence de
Supabase est précise sur lequel : les clés publiables et secrètes voyagent dans
apikey plutôt que dans Authorization: Bearer, parce que ce sont de courtes
chaînes opaques et que tout ce qui tente de les vérifier comme un JWT échoue.
Cette clé a sa place dans votre application. Elle dit à quel projet appartient une requête, et ce qu'un visiteur qui la détient atteint est décidé par les règles de vos tables, ce qui est toute la raison pour laquelle elle peut être publique. Quelles clés d'API sont sûres dans votre frontend parcourt le reste de la famille.
Le correctif qui aggrave tout
La clé secrète entre dans le même en-tête, et sur un projet ancien elle fait taire le message et continue de fonctionner.
C'est un seul clic mal placé. Votre tableau de bord liste les deux clés sur la même page, et l'ancienne paire se ressemble : même forme, même longueur, l'une sous l'autre. La gravité du clic dépend de la paire que votre projet délivre.
Une clé secrète récente est refusée dans un navigateur. Supabase bloque
sb_secret_… en regardant l'en-tête User-Agent et répond 401. En coller une
dans votre frontend ne fait donc pas disparaître l'erreur, ce qui est le meilleur
résultat possible ici.
Une ancienne clé service_role n'a pas ce blocage. Elle fonctionne. La liste
se remplit, l'application se comporte comme la semaine dernière, et la clé se
trouve désormais dans un fichier que n'importe quel visiteur peut télécharger.
Là, elle ignore toutes les règles que vous avez écrites : service_role porte
l'attribut Postgres BYPASSRLS, une politique ne s'y applique donc jamais.
Chaque ligne de chaque table, lisible et modifiable par celui qui ouvre le
fichier.
La note de Supabase sur le blocage navigateur mérite deux lectures. Le blocage répond 401, et un attaquant peut toujours se servir de la clé avec d'autres outils. Ce qui est protégé, ce sont les requêtes de votre propre application ; la même clé dans le même fichier répond à une requête envoyée depuis un script.
L'autre fausse piste, et pourquoi elle est si facile
Assouplir une règle sur la table fait taire le message elle aussi, et cela change qui peut lire vos lignes.
Voici ce fil Supabase en entier, regroupé selon ce que chaque réponse vous demande de changer :
| Quoi changer | Combien des huit réponses | Ce que cela change vraiment |
|---|---|---|
| La Site URL sous Auth | 1 | Où atterrit une redirection |
| Une politique ou un grant | 3 | Qui lit et écrit vos lignes |
| La requête ou la session | 3 | Votre propre code |
L'en-tête apikey | 1 | Ce que la porte demandait |
La dernière ligne est celle qui répond à l'indice contenu dans le message. C'est le commentaire du bas du fil, et il a une seule voix.
Aucune des sept autres n'est écrite de mauvaise foi, et c'est ce qui rend la chose difficile. Un même message apparaît vraiment dans plusieurs situations, la personne qui répond a vraiment remis son application en marche, et une politique qui manquait déjà est vraiment quelque chose à corriger. Ce qui déraille, c'est l'ordre : on réécrit une règle pour faire taire un message qui parle d'un en-tête, et la règle est la moitié de cette paire que personne ne vérifie ensuite. Votre application fonctionne dans les deux cas, donc rien ne vous dit lequel des deux vous avez fait.
Vu de l'extérieur, le résultat se voit. Notre scanner lit les applications en ligne comme le ferait un inconnu, et sur les 31 056 applications qu'il a notées, trois ont publié une clé secrète Supabase. Cela en fait la trouvaille la plus rare que nous ayons, et la plus grave.
Le réflexe voisin est bien plus répandu. Derrière 8 435 de ces applications nous avons trouvé un projet Supabase. Sur 3 680 d'entre elles la question « un inconnu peut-il lire cette table » a seulement obtenu une réponse exploitable, et 2 096 ont répondu oui : au moins une table a livré des lignes à une requête venue de l'extérieur, sans aucun compte. Dans 394 cas la table ouverte portait le nom d'une table de personnes.
Une partie est délibérée. Un menu publié, une page d'annonces et un journal des versions public vivent tous dans des tables qu'un inconnu doit pouvoir lire, et notre scanner ne sait pas les distinguer d'une table de clients ouverte par inadvertance. Il rapporte ce qui a répondu et vous laisse le jugement, ce qui est la raison pour laquelle activer Row Level Security ne revient pas à être protégé.
Si vous préférez voir votre propre réponse plutôt que la déduire, notre scan gratuit lit votre site en ligne et vous dit ce qu'il atteint depuis l'extérieur. Il prend une vingtaine de secondes et ne demande aucun compte : scanner votre application.
Comment savoir quelle clé vous avez collée
Lisez le début de la chaîne.
sb_publishable_… a sa place dans votre application. sb_secret_… jamais. Il
n'y a rien à décoder, puisque ce à quoi sert la clé est écrit sur sa façade.
Une longue valeur commençant par eyJ fait partie de l'ancienne paire, et c'est
là que les deux deviennent difficiles à séparer. La section du milieu d'une telle
clé est de l'information lisible et non du chiffrement, et elle porte un champ
qui tranche la question :
{
"iss": "supabase",
"role": "anon", ← celui qui compte
"iat": 1750000000
}
anon est la publiable de la paire. service_role est celle à faire tourner
aujourd'hui. La documentation de Supabase le dit plus sèchement que nous ne le
dirions : si un tutoriel ou un assistant IA vous dit de copier une longue clé qui
commence par eyJ, il a été écrit pour les anciennes clés.
Les deux paires peuvent être actives en même temps, et c'est le détail sur lequel
les gens trébuchent. Créer une clé publiable ou secrète laisse vos clés anon et
service_role exactement là où elles étaient, et elles continuent de fonctionner
jusqu'à ce que vous les désactiviez dans le tableau de bord, ce qui est une étape
séparée.
Ce qui a changé avec les nouvelles clés couvre
cette migration, et
où vit chaque valeur dans votre tableau de bord
est la page à ouvrir si vous n'y êtes jamais allé.
Si la clé secrète est déjà dans votre application
Faites-la tourner avant de modifier quoi que ce soit, et travaillez dans l'ordre donné par Supabase.
Créez une nouvelle clé secrète dans le tableau de bord. Remplacez l'ancienne
partout où votre projet l'utilise. Vérifiez que chaque partie de votre
application est passée à la nouvelle. Alors seulement retirez l'ancienne, et la
dernière étape diffère selon la paire que vous détenez : supprimer une clé
secrète est définitif, tandis que désactiver une ancienne paire anon et
service_role est réversible, vous pouvez donc les rallumer si un client vous a
échappé.
Faire tourner la clé d'abord ou colmater la fuite d'abord dépend de savoir si une copie est déjà publique. Faire tourner une clé service_role Supabase parcourt les deux ordres et ce qu'il faut lire ensuite dans vos journaux.
Pour que ça tienne une fois l'erreur disparue
La correction tient jusqu'au prochain prompt ou à la prochaine modification qui touche votre configuration Supabase. Il suffit d'un prompt pour remettre une clé en place ou relâcher de nouveau une règle, et votre application continue de fonctionner dans les deux cas, si bien que le changement n'apparaît que lorsque quelqu'un regarde.
Reeve Monitor regarde pour vous :
- les neuf vérifications toutes les heures, sur trois applications au plus
- un e-mail le jour où un nouveau scan note votre application plus bas que le précédent
- si l'application répond, toutes les 60 secondes
- un rapport mensuel de ce qu'il a vu
Une clé qui peut écrire chaque ligne peut aussi vider chaque table. Reeve Care garde une copie de votre base Supabase là où aucune clé de votre application ne peut l'atteindre.
- une copie chiffrée chaque jour, gardée hors de votre compte Supabase
- chaque copie vérifiée avant de compter, en dénombrant les lignes de chaque table
- une restauration en un clic qui sauvegarde l'état actuel avant de remplacer quoi que ce soit
- vos fichiers téléversés aussi, dès que vous connectez un accès Storage
- tout ce que fait Monitor
Ce qu'il faut faire aujourd'hui
Que faire
- Lisez le message comme un refus à la porte. Vos lignes n'ont pas été touchées et il n'y a rien à récupérer.
- Regardez dans l'onglet réseau du navigateur la requête en échec et vérifiez si un en-tête
apikeyest seulement présent. Ce seul coup d'oeil vous dit si c'est un problème de clé ou de redirection. - Créez votre client Supabase avec la clé publiable et laissez la bibliothèque envoyer l'en-tête.
sb_publishable_…sur un projet récent, la cléanonsur un ancien. - Si une clé secrète est déjà dans votre application, faites-la tourner d'abord. L'effacer du code ne ferme pas la porte, puisque l'ancienne version reste dans votre historique de versions et dans toute copie en cache de votre site.
- Remettez en place toute règle assouplie pendant cette chasse, puis vérifiez-la depuis l'extérieur plutôt que depuis l'aperçu de votre builder.
Commencez par la table d'où venait l'erreur, puis lisez les politiques de chaque table touchée la même semaine. La checklist de sécurité en 10 minutes couvre ce qui reste habituellement ouvert dans une application fraîchement lancée, et le guide de sécurité Supabase parcourt le reste de ce qu'un inconnu peut atteindre.
FAQ
Que veut dire "No API key found in request" ?
Cela veut dire que votre requête est arrivée chez Supabase sans en-tête apikey, donc la porte que Supabase place devant votre projet l'a refusée avant que votre base de données soit impliquée. Rien n'a été lu, rien n'a été écrit, et rien n'a changé dans vos données. La réponse porte un indice qui dit la même chose autrement : no apikey request header or url param was found.
Quelle clé Supabase va dans l'en-tête apikey ?
Votre clé publiable, c'est-à-dire sb_publishable_ sur un projet récent et la clé anon sur un projet plus ancien. Supabase la fournit exactement pour cela et s'attend à la voir dans votre application. La bibliothèque cliente la place dans l'en-tête à chaque requête, donc si vous créez le client avec la clé publiable vous ne tapez jamais cet en-tête vous-même.
Est-ce sûr de laisser ma clé anon dans le navigateur ?
Oui, et elle doit y être pour que votre application puisse parler à votre projet. La clé anon dit seulement à quel projet appartient une requête ; ce qu'un visiteur qui la détient atteint vraiment est décidé par les règles Row Level Security de vos tables. Quelles clés d'API sont sûres dans votre frontend parcourt toute la famille des clés et explique comment les lire.
J'ai utilisé la clé service_role et ça a marché. Est-ce un problème ?
Oui, et c'est ce qui vaut la peine d'être traité aujourd'hui. Une clé service_role porte l'attribut Postgres BYPASSRLS, vos politiques ne s'y appliquent donc jamais. Livrée dans votre application, elle se trouve dans un fichier que n'importe quel visiteur peut télécharger, et celui qui le télécharge peut lire et écrire chaque ligne de chaque table. Faites tourner la clé d'abord, puis déplacez vers un serveur ce qui en avait besoin.
Comment savoir quelle clé j'ai collée ?
Lisez le début de la chaîne. sb_publishable_ a sa place dans votre application et sb_secret_ jamais. Une longue valeur commençant par eyJ fait partie de l'ancienne paire, et ces deux-là se ressemblent, il faut donc regarder dedans : la section du milieu se décode en données lisibles portant un champ role, qui indique anon ou service_role.