Bases de la sécurité
Supabase invalid JWT : quelle clé a cessé de marcher ?
Une erreur Supabase invalid JWT, c'est PGRST301 : un jeton signé n'a pas passé la vérification. Sur un projet récent, c'est la session, pas la clé API.

En bref
- Une erreur Supabase invalid JWT arrive sous le code PGRST301 avec un HTTP 401, et elle signifie que la signature du jeton envoyé n'a pas pu être vérifiée. Rien n'a été cherché dans une liste puis refusé.
- Les clés publishable et secret sont de courtes chaînes sans signature. Sur un projet qui est passé à ces clés, cette erreur concerne donc votre utilisateur connecté et jamais la clé API.
- PostgREST 13 a remplacé une phrase vague par une courte liste de phrases précises. Lisez le message que vous avez vraiment reçu : chacun écarte une cause différente.
Hier votre application marchait. Ce matin chaque requête revient en 401, rien n'a changé dans votre code, et la réponse parle d'un JWT, une chose que vous n'avez jamais tapée nulle part.
{
"code": "PGRST301",
"message": "JWT cryptographic operation failed"
}
Une erreur Supabase invalid JWT est celle qui arrive après que vous avez fait ce qu'il fallait. Vous avez changé une clé parce qu'un guide vous l'a conseillé, ou vous avez fait passer votre projet aux nouvelles clés, et l'application s'est tue.
Et voici ce que guide après guide se trompe : la clé dont parle ce message
n'est souvent pas celle que vous venez de changer. Cherchez l'erreur et on vous
renverra vers votre clé anon. Sur un projet passé aux clés publishable et
secret, la clé API n'est pas du tout un jeton signé, donc ce qui a échoué à la
vérification était autre chose dans la requête.
Que signifie une erreur Supabase invalid JWT ?
Quelque chose dans votre requête était un jeton signé, et la signature ne correspondait pas.
Un mot de passe est un mot que quelqu'un cherche dans une liste. Un JWT, non. C'est une lettre fermée par un sceau de cire : la personne à la porte ne vous cherche nulle part, elle tient un cachet et vérifie si le sceau de votre lettre a été fait avec. Si oui, vous passez, et personne ne lit de liste.
Une lettre scellée avec le mauvais cachet est refusée alors que chaque mot à l'intérieur est correct. Et dès que la porte change de cachet, plus aucune lettre déjà partie ne s'ouvre, sans que personne ait touché aux lettres.
Votre requête est la lettre. Votre projet Supabase tient le cachet. Un 401 ici veut dire que les deux ne concordaient pas.
Le message a changé dans PostgREST 13, le code non
Faites votre test sur PGRST301. La phrase qui l'accompagne dépend de la version
de PostgREST que fait tourner votre projet, et les plus récentes vous en donnent
une courte liste.
La référence des erreurs de PostgREST décrit PGRST301 comme un JWT fourni qui
n'a pas pu être décodé ou qui est invalide, en HTTP 401, aussi bien en version 13
qu'en version 14. Cette phrase est la description du tableau de documentation.
Ce n'est pas ce qui arrive dans votre champ message. D'après Error.hs sur la
branche main de PostgREST, ce champ porte l'une de ces phrases :
| Ce que dit le message | Ce que cela vous apprend |
|---|---|
Empty JWT is sent in Authorization header | L'en-tête a bien été envoyé et il était vide. |
Expected 3 parts in JWT; got 1 | Ce qui est arrivé n'est pas un JWT. Un JWT fait trois blocs séparés par des points. |
No suitable key or wrong key type | Le serveur n'a aucune clé capable de vérifier un jeton de ce type. |
Wrong or unsupported encoding algorithm | Le jeton annonce un algorithme de signature que le serveur n'utilise pas. |
JWT cryptographic operation failed | Le jeton est bien formé et la signature ne correspond pas. |
Unsupported token type | Le jeton a une forme que PostgREST ne lit pas. |
JWT cryptographic operation failed est celui qui veut dire clé périmée. Le
jeton a été construit correctement et signé par quelque chose que le projet ne
détient plus, ce qui est exactement ce qu'une rotation laisse derrière elle.
Les projets plus anciens voient tout autre chose, et c'est de là que vient la
chaîne que l'on retrouve partout dans les résultats de recherche. Avant la
version 13, PostgREST affichait ce que sa bibliothèque JWT avait dit,
exactement comme elle l'avait dit. D'où JWSError JWSInvalidSignature : c'est
une valeur de bibliothèque affichée telle quelle, pas une phrase écrite pour
vous. Le changelog de PostgREST pour la 13.0.0 décrit ce changement comme des
messages d'erreur diagnostiques à la place d'internes exposés.
Un code voisin mérite d'être connu au passage. PGRST303, également en 401, est
ce que vous obtenez quand la signature a été vérifiée et que le contenu du jeton
a été refusé : expiré, pas encore valide, ou portant la mauvaise audience. Si
c'est votre code, arrêtez de regarder vos clés et allez regarder l'horloge et la
session.
Quelle clé est en cause : votre clé API ou le jeton de votre utilisateur ?
Cela dépend de si votre projet utilise encore anon et service_role.
La documentation de Supabase sur les clés API est explicite sur la différence.
Les anciennes clés anon et service_role sont des JWT de longue durée, et
c'est pour cela qu'elles commencent par eyJ. Les clés publishable et secret
plus récentes sont de courtes chaînes et ne sont pas du tout des JWT.
Ce seul fait coupe l'erreur en deux :
- Votre projet utilise encore
anonetservice_role. La clé API de votre requête est elle-même un jeton signé, donc une clé venue d'un autre projet, une clé d'avant une rotation ou un collage tronqué échouent à la vérification de signature exactement comme une mauvaise session. Chacun des deux jetons de la requête peut être celui qui a échoué. - Votre projet est passé à
sb_publishable_etsb_secret_. La clé API a cessé d'être un jeton, il n'y a donc plus de signature dessus que quoi que ce soit puisse refuser. Reste à vérifier le jeton d'accès de votre utilisateur connecté, que Supabase Auth délivre séparément de la clé API.
En pratique, la première question n'est donc pas de savoir quelle clé est mauvaise. C'est de savoir si quelqu'un était connecté quand la requête a échoué. Si une page hors connexion fonctionne et qu'une page connectée ne fonctionne pas, vous regardez des sessions. Si tout échoue, y compris une page qui ne demande rien de personnel, vous regardez la clé API, et votre projet est un ancien.
Si ce sont des sessions et que votre log Auth affiche aussi
Invalid Refresh Token, vos utilisateurs sont déconnectés parce que
la session derrière leur token a pris fin.
Si la réponse vient de Supabase Auth et dit invalid claim: missing sub claim,
le token a passé la vérification de sa signature et ne nommait personne. C'est
une clé envoyée là où devrait se trouver le token d'une personne, et
ce message a son propre article.
Invalid JWT n'est pas "No API key found in request"
Ce sont deux messages différents avec deux causes différentes, et les corrections n'ont rien en commun.
Un invalid JWT veut dire qu'un jeton est arrivé et que la signature ne
correspondait pas. "No API key found in
request" veut dire qu'aucun en-tête apikey
n'est arrivé, et que la requête a donc été refusée à la passerelle avant que
votre base de données soit interrogée.
La réponse elle-même tranche. Une clé manquante est refusée par la passerelle
placée devant votre projet, le corps porte donc un message et un hint et
aucun code. Un invalid JWT est allé assez loin pour être lu par PostgREST, et
c'est pour cela qu'il revient avec PGRST301 écrit dessus.
Où traîne encore l'ancienne clé, dans l'ordre
Quatre endroits, à vérifier dans cet ordre parce qu'il correspond à peu près à la fréquence avec laquelle chacun est la réponse.
- Les réglages de projet de votre outil de création. Lovable, Bolt, v0 et Replit gardent chacun leur jeu de variables d'environnement, et si vous avez construit avec Cursor ou Claude Code, le même jeu se trouve là où vous déployez. C'est la copie que la plupart des gens mettent à jour, et la seule que la plupart des gens mettent à jour.
- Une clé écrite directement dans le code. Elle part dans le JavaScript que vos visiteurs téléchargent, donc la changer dans le tableau de bord ne change rien tant que le fichier n'est pas modifié et le site reconstruit. Où se trouve cette page et à quoi sert chaque valeur, si vous ne l'avez pas ouverte.
- Les secrets d'Edge Function. Une fonction garde son propre jeu, séparé de celui de votre frontend, et continue donc d'utiliser l'ancienne clé une fois que tout le reste a bougé. Supabase prévient aussi qu'une fonction dont le réglage Verify JWT est activé peut casser lors de la rotation d'une clé de signature.
- Un déploiement que personne n'a reconstruit. La clé a été figée lors du dernier build, la copie en ligne continue donc de l'envoyer jusqu'à ce que quelque chose déclenche un nouveau build. Votre tableau de bord affiche la nouvelle clé pendant tout ce temps, et c'est ce qui rend ce cas difficile à voir : l'endroit où vous allez vérifier est le seul qui soit déjà correct.
Quelle clé votre application en ligne envoie vraiment
Ouvrez le site publié plutôt que l'aperçu, et lisez ce que la page envoie.
Une ancienne clé Supabase est une longue chaîne qui commence par eyJ. Les plus
récentes s'annoncent dans leurs premiers caractères : sb_publishable_ a sa
place dans votre application et sb_secret_ jamais. Si vous trouvez la seconde
dans une page qu'un visiteur peut charger, arrêtez cet article et
changez-la avant toute autre chose.
Laquelle des deux vous avez en main, et
ce qui a changé quand Supabase les a
renommées, sont à régler avant de commencer à
remplacer des valeurs.
Pour voir ce que votre projet délivre en ce moment : toutes les clés vivent désormais au même endroit dans le tableau de bord, sous Settings puis API Keys, les anciennes comprises.
Si vous préférez ne pas lire un bundle minifié, notre scan gratuit lit votre application en ligne comme le ferait un inconnu et signale les clés visibles depuis l'extérieur. Il prend une vingtaine de secondes et ne demande aucun compte : scanner votre application.
Faire une rotation de clé de signature sans déconnecter tout le monde
Lequel des deux chemins de rotation vous prenez décide si vos utilisateurs s'en aperçoivent.
La documentation de Supabase sur les clés de signature met les deux côte à côte.
La rotation de l'ancien secret JWT déconnecte immédiatement les utilisateurs
actifs, et anon et service_role doivent être changées en même temps. Les
JWT signing keys plus récentes marchent autrement : une nouvelle clé démarre
en standby, la rotation en fait la clé courante, et celle qu'elle remplace
devient la précédente. Tant que c'est le cas, les signatures des deux clés
continuent d'être acceptées, et les lettres scellées la semaine dernière
s'ouvrent encore. Révoquer l'ancienne clé est l'étape distincte qui met fin à
cela.
Deux avertissements de cette même documentation sont ceux qui piègent :
- Un composant qui vérifie lui-même les jetons contre l'ancien secret JWT, avec une bibliothèque comme jose ou jsonwebtoken, casse à la rotation. Il tient l'ancien cachet et personne ne lui a dit d'en changer.
- Une Edge Function dont le réglage Verify JWT est activé peut casser pour la même raison.
Créer des clés publishable et secret ne révoque pas non plus vos anciennes clés. Elles continuent de fonctionner jusqu'à ce que vous les désactiviez, ce que la documentation présente comme une étape à part. Une application qui n'a migré que la moitié de ses copies continue donc de tourner pendant des semaines, et le jour où quelqu'un désactive l'ancienne paire est le jour où l'autre moitié se met à répondre 401.
Surveiller une application qui s'est mise à répondre 401
Une application qui renvoie 401 à chaque requête est toujours en ligne, sert toujours une page et répond toujours à un contrôle de santé. Rien ne passe au rouge dans votre tableau de bord. Vous l'apprenez quand quelqu'un vous écrit.
Reeve Monitor l'apprend en premier :
- les neuf vérifications toutes les heures, sur jusqu'à trois applications
- un e-mail le jour où un nouveau scan note votre application moins bien 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é que vous avez dû changer dans l'urgence est en général une clé qui traînait là où elle n'aurait pas dû, et d'ici vous ne voyez pas ce qui a été lu ou modifié avec elle pendant qu'elle était dehors. Reeve Care garde une copie de votre base de données Supabase d'avant cet épisode.
- une copie chiffrée selon un calendrier, gardée sur le stockage de Reeve dans l'UE, en dehors de votre compte Supabase
- chaque copie vérifiée avant d'être comptée, en comptant les lignes de chaque table
- une restauration en un clic qui sauvegarde l'état actuel avant de le remplacer
- vos fichiers téléversés aussi, dès que vous connectez un accès Storage
- tout ce que fait Monitor
Quoi faire aujourd'hui
Que faire
- Lisez le champ
code, pas la phrase.PGRST301est une signature qui ne correspondait pas,PGRST303un jeton dont le contenu a été refusé, par exemple expiré. - Déterminez quel jeton a échoué avant de changer une clé. Si les pages hors connexion marchent et que les pages connectées ne marchent pas, c'est une session et non votre clé API.
- Vérifiez les quatre endroits où se cache l'ancienne clé : les réglages de l'outil de création, le code lui-même, les secrets d'Edge Function, et un déploiement que personne n'a reconstruit.
- Faites une rotation d'un seul tenant. Chaque copie oubliée est une application qui marche à moitié, et le site en ligne continue d'envoyer l'ancienne clé jusqu'à sa reconstruction.
- Avec les nouvelles clés de signature, c'est la révocation qui met vraiment fin aux anciennes signatures. La rotation seule les laisse acceptées.
Commencez par demander si une page hors connexion se charge encore, car cette seule réponse décide quelle moitié de cet article vous concerne. S'il s'avère qu'une clé secrète traînait à découvert, la changer couvre l'ordre dans lequel procéder, et quelles clés ont jamais été sûres à livrer explique pourquoi les deux se ressemblent autant.
FAQ
Que signifie PGRST301 ?
PGRST301 est la réponse de PostgREST en HTTP 401 parce qu'un jeton signé présent dans votre requête n'a pas pu être vérifié. PostgREST est le serveur qui transforme vos tables Supabase en l'API HTTP avec laquelle votre application parle. Sa propre référence des erreurs décrit ce code comme un JWT fourni qui n'a pas pu être décodé ou qui est invalide, et depuis la version 13 le champ message porte une phrase plus précise indiquant quelle partie de la vérification a échoué.
Pourquoi ai-je un 401 après avoir changé ma clé Supabase ?
Parce qu'une copie de l'ancienne clé est encore envoyée quelque part. Les endroits habituels sont les réglages de projet de votre outil de création, une clé écrite directement dans le code, un secret d'Edge Function, et un déploiement construit avant votre changement et jamais reconstruit depuis. Un jeton signé par une clé que le serveur n'accepte plus échoue à la vérification, et cela vous arrive comme un 401 sur chaque requête plutôt que comme un message sur la clé.
Quelle différence avec le message No API key found in request ?
Ce sont deux messages différents avec deux causes différentes. No API key found in request signifie que la requête est arrivée sans aucun en-tête apikey et qu'elle a été refusée à la passerelle avant même que votre base de données soit interrogée. Un invalid JWT signifie qu'un jeton était bien là et que sa signature ne correspondait pas. L'un est une lettre sans adresse, l'autre une lettre avec le mauvais sceau.
Dois-je mettre à jour mes Edge Functions aussi ?
Oui, et c'est la copie que l'on oublie le plus souvent, car une Edge Function garde ses propres secrets, séparés de ceux de votre frontend. Supabase prévient aussi qu'une fonction dont le réglage Verify JWT est activé peut casser lors de la rotation d'une clé de signature, et que tout composant qui vérifie lui-même les jetons contre l'ancien secret JWT cassera pour la même raison.
Comment voir quelle clé mon application en ligne envoie vraiment ?
Ouvrez votre site publié, pas l'aperçu, et regardez ce que la page envoie réellement. Une ancienne clé Supabase est une longue chaîne qui commence par eyJ, les plus récentes commencent par sb_publishable_ ou sb_secret_. Notre scan gratuit lit une application en ligne comme le ferait un inconnu et signale les clés visibles depuis l'extérieur, ce qui vous dit aussi si une ancienne est encore envoyée.