Aller au contenu

Bases de la sécurité

Supabase "Auth session missing!" : où est passée la session

"Auth session missing!" : supabase-js n'a trouvé aucune session connectée et n'a même pas interrogé Supabase. Où est passée la session, et la solution.

Vlad Tkachenko12 min de lecture
Un porte-badge vide au bout d'un cordon, la place du badge cernée d'ambre, devant deux badges qui portent un visage.

En bref

  • "Auth session missing!" (AuthSessionMissingError, statut 400) vient de supabase-js, dans votre application. La bibliothèque a cherché une session connectée, n'en a trouvé aucune et n'a jamais envoyé la requête à Supabase.
  • Sur une page où personne ne s'est connecté, c'est la bonne réponse, et votre code n'a qu'à la traiter. Sur une page de réinitialisation du mot de passe, cela veut dire que le lien n'a connecté personne ici. Dans une Edge Function ou sur un serveur, la session du visiteur n'a jamais été transmise.
  • Gardez getUser(). Remplacer par getSession() sur un serveur fait disparaître l'erreur en croyant tout ce que dit le cookie.

Votre application Lovable ou Bolt connecte les gens avec Supabase. Un matin, vous ouvrez la console du navigateur et trouvez ceci en rouge, sur une page où rien n'a l'air faux :

AuthSessionMissingError: Auth session missing!

Ou une cliente vous écrit que la page « Choisir un nouveau mot de passe » la refuse avec ces mêmes mots, à chaque fois.

Cherchez « auth session missing supabase » et vous trouverez des fils remplis de solutions : remplacer getUser() par getSession(), revenir à une ancienne version de la bibliothèque, ajouter un délai, redémarrer le projet. Elles supposent que quelque chose s'est cassé entre votre application et Supabase. Aucune requête n'est partie. L'erreur naît dans votre application, écrite par la bibliothèque Supabase, avant que quoi que ce soit ne quitte le navigateur.

Que veut dire "Auth session missing!" dans Supabase ?

On a demandé à supabase-js qui était connecté ; elle a cherché une session, n'en a trouvé aucune et a répondu elle-même.

Une session, c'est ce que Supabase Auth remet à une personne quand elle se connecte : une paire de jetons qui disent qui elle est, gardés par le navigateur pour que la page suivante la reconnaisse encore. supabase-js est la bibliothèque avec laquelle votre application parle à Supabase, et chaque appel qui a besoin d'une personne commence par sortir cette session.

Le message est écrit dans supabase-js elle-même, dans le fichier errors.ts de la bibliothèque d'authentification : une AuthSessionMissingError avec le texte Auth session missing! et le statut 400. Ce 400 est une étiquette que la bibliothèque se donne. Aucun serveur n'a répondu ainsi, et les journaux de Supabase ne montreront rien, parce que getUser() cherche une session avant toute autre chose. Un commentaire dans son code source le dit : elle renvoie une erreur s'il n'y a ni jeton d'accès ni en-tête Authorization personnalisé.

Imaginez un accueil capable de vous dire quel visiteur est qui. Chaque personne qui se connecte reçoit un badge à l'entrée, et supabase-js garde ce badge dans la poche du navigateur. Quand votre code demande « qui est-ce ? », supabase-js met d'abord la main à la poche. S'il y a un badge, elle l'apporte à l'accueil et revient avec un nom. Si la poche est vide, elle ne va même pas jusqu'à l'accueil. Elle revient aussitôt et vous dit qu'il n'y a pas de badge.

Avec une session, supabase-js interroge Supabase Auth. Sans session, elle s'arrête dans votre application, et l'erreur vient de là.

Elle est renvoyée, pas levée

getUser() renvoie l'erreur à côté d'un utilisateur vide, et c'est votre code qui décide de la suite.

L'appel ressemble à ceci, et sans session user vaut null et error contient l'AuthSessionMissingError :

const { data: { user }, error } = await supabase.auth.getUser()

Rien n'est levé, donc un try … catch autour de l'appel n'attrape rien. C'est de là que vient le dégât visible, sous l'une de deux formes :

  • Le code affiche toute error qu'il reçoit. Un visiteur qui ne s'est pas encore connecté voit « Auth session missing! » dans une notification ou un encadré rouge, sur une page qu'il avait le droit de voir.
  • Le code utilise l'utilisateur sans vérifier. Il lit user.id et la page plante avec Cannot read properties of null, ce qui ressemble à un tout autre bug.

Les deux sont la même erreur : traiter « personne n'est connecté » comme une panne. Vérifiez d'abord l'utilisateur, et donnez à un visiteur non connecté l'écran prévu pour lui :

const { data: { user } } = await supabase.auth.getUser()
if (!user) {
  // personne n'est connecté : afficher l'écran de connexion ou la page publique
  return
}

Si vous voulez distinguer ce cas d'une vraie panne, la bibliothèque le nomme : error.name vaut 'AuthSessionMissingError'.

Où la session se perd

À trois endroits, et seul le premier est sans danger.

1. Personne ne s'est connecté dans ce navigateur. Votre application appelle getUser() sur une page que tout le monde peut ouvrir, par exemple pour choisir entre « Se connecter » et « Mon compte ». Pour un visiteur, la poche vide est la réponse exacte. C'est là que la plupart des gens rencontrent le message, et la correction du code ci-dessus suffit.

2. Une page de réinitialisation du mot de passe ou de lien magique. Choisir un nouveau mot de passe appelle updateUser({ password }), et le guide des mots de passe de Supabase est clair sur l'ordre : c'est une fois la session obtenue qu'on recueille le nouveau mot de passe et qu'on appelle updateUser. Sur cette page, la session doit venir du lien de l'e-mail, qui connecte la personne en s'ouvrant. Quand il ne le fait pas, la poche est vide et updateUser() répond « Auth session missing! ». Les raisons habituelles :

  • Le lien avait déjà servi, ou avait expiré, avant d'être ouvert. Pourquoi Supabase dit que le lien de l'e-mail est invalide ou a expiré passe en revue chacune.
  • Il a été ouvert dans un autre navigateur ou une autre application que celui qui l'a demandé. Quand une application utilise la connexion PKCE de Supabase, une partie de l'échange reste dans le navigateur qui l'a commencé, et selon la documentation de Supabase l'échange du code doit être lancé dans le même navigateur et sur le même appareil que ceux où le parcours a commencé.
  • Il a envoyé la personne ailleurs, par exemple sur localhost, si bien que la session est arrivée à une adresse qui n'est pas votre application. Pourquoi Supabase redirige vers localhost traite ce cas.

3. Votre serveur ou une Edge Function n'a jamais reçu la session. Le badge est dans le navigateur de votre visiteur. Le code qui tourne ailleurs, une Edge Function Supabase ou la moitié serveur d'une application Next.js faite avec v0 ou Cursor, crée son propre client Supabase, et un nouveau client commence avec une poche vide. Tant que la session du visiteur ne lui est pas transmise, getUser() n'y trouve rien et répond « Auth session missing! » sans interroger Supabase.

Dans une Edge Function, la session voyage dans l'en-tête Authorization de la requête, et supabase-js ne l'utilise que si vous la lui passez en créant le client. Dans une application Next.js, elle voyage dans des cookies, que l'outil serveur de Supabase ne lit que si vous lui donnez ceux de la requête. Si une Edge Function renvoie cet échec comme une erreur, votre application affiche "Edge Function returned a non-2xx status code", avec ce message qui attend dans le journal de la fonction.

La session appartient au navigateur. Une fonction ou un serveur ne l'a que si la requête l'apporte et que le code la transmet à son client.

Comment savoir quel cas est le vôtre

Regardez où le message apparaît, puis ce que le navigateur a réellement envoyé.

Dans la console du navigateur, sur une page ouverte à tous. Cas 1. Ouvrez les outils de développement de votre navigateur (F12), choisissez Application, puis Local Storage et l'adresse de votre application. supabase-js garde la session sous une clé formée de sb-, de l'identifiant de votre projet et de -auth-token. S'il n'y a pas d'entrée de ce genre, ce navigateur n'a pas de session, et le message dit vrai. Une application Next.js qui utilise les outils serveur de Supabase garde la session dans des cookies à la place : cherchez le même nom sous Cookies.

Sur une page de réinitialisation ou de lien magique. Cas 2. Regardez l'adresse sur laquelle le lien s'est ouvert. Si ce n'est pas votre application, ou si c'est un lien que vous ou un scanner de messagerie aviez déjà ouvert, la session n'a jamais eu la moindre chance d'arriver.

Dans le journal d'une Edge Function, ou de votre serveur. Cas 3. Ouvrez l'onglet Network, refaites l'action et cliquez sur la requête vers votre fonction. Si son en-tête Authorization contient une longue chaîne qui commence par eyJ, le navigateur a envoyé le jeton d'une personne, et votre fonction ne l'a pas transmis à son client.

Ce que vous ne trouverez sur aucune voie, c'est une requête vers /auth/v1/user revenue avec ce message. Si vous en voyez une qui a échoué, c'est une autre erreur. Une réponse disant invalid claim: missing sub claim signifie que la clé de votre projet a été envoyée là où devait se trouver une personne, et cet article est son jumeau.

La solution pour chaque cas

Donnez à chacun la session qui lui manque, ou une réponse qui n'en a pas besoin.

Cas 1, le visiteur non connecté : vérifiez user avant de l'utiliser, comme dans le code ci-dessus, et affichez l'écran prévu pour quelqu'un qui ne s'est pas connecté.

Cas 2, la page de réinitialisation : attendez que le lien ait connecté la personne avant d'afficher le formulaire. Supabase émet un événement PASSWORD_RECOVERY quand quelqu'un arrive par un lien de récupération, et sa référence montre la page qui l'attend :

supabase.auth.onAuthStateChange((event) => {
  if (event === 'PASSWORD_RECOVERY') {
    // le lien a connecté la personne : afficher maintenant le formulaire du nouveau mot de passe
  }
})

Si l'événement ne vient jamais, le lien n'a pas fonctionné. Dites-le et proposez d'en envoyer un nouveau, plutôt que d'afficher un formulaire voué à l'échec.

Cas 3, la fonction ou le serveur : transmettez la session du visiteur au client que vous créez. Dans une Edge Function, créez-le avec l'en-tête reçu :

const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_ANON_KEY')!,
  { global: { headers: { Authorization: req.headers.get('Authorization')! } } },
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
  return new Response(JSON.stringify({ error: 'Sign in first' }), {
    status: 401,
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
}

Le wrapper plus récent de Supabase, withSupabase({ auth: 'user' }), fait la même chose avant que votre gestionnaire ne s'exécute, et l'article sur les Edge Functions montre la mise en place. Gardez le 401 pour un appelant sans session, afin qu'une requête venue d'une page non connectée reçoive un refus clair. Avec l'en-tête transmis, getUser() peut y renvoyer à la place invalid claim: missing sub claim pour un appelant non connecté : le même visiteur vu de l'autre côté, et le 401 couvre les deux.

Dans une application Next.js, créez le client serveur avec l'outil @supabase/ssr de Supabase et les cookies de la requête, comme le montre sa mise en place côté serveur, et appelez getUser() sur ce client.

Deux solutions qui aggravent les choses

Les deux sont proposées pour ce message, et chacune arrête l'erreur en retirant la vérification qui se trouve derrière.

Remplacer getUser() par getSession(). Dans le navigateur, cela ne change rien d'important : le stockage est toujours vide, vous n'obtenez ni session ni utilisateur, simplement sans le message. Sur un serveur, c'est dangereux. Le guide côté serveur de Supabase dit de ne jamais faire confiance à supabase.auth.getSession() dans du code serveur, parce qu'elle lit la session dans le cookie sans la revalider. Un cookie, c'est le visiteur qui vous l'envoie, et n'importe qui peut vous envoyer n'importe quel cookie. getUser() est la méthode dont supabase-js documente qu'elle fait une requête au serveur de Supabase Auth, si bien que son résultat est authentique. Gardez-la partout où la réponse décide de ce que quelqu'un a le droit de faire.

Mettre la clé secrète dans le client pour que l'appel cesse d'échouer. Un client créé avec la clé service_role ou sb_secret_ n'a pas besoin de session pour lire et écrire, parce qu'il passe outre toutes les règles de votre base. La fonction remarche, pour tout appelant, connecté ou non. Si un builder fait ce changement dans du code qui part vers le navigateur, la clé part avec, chez chaque visiteur. Si c'est déjà fait, faites-la tourner d'abord.

Cela se vérifie de l'extérieur. Notre scan gratuit lit le JavaScript de votre application en ligne comme le ferait un inconnu et nomme chaque clé qu'il trouve, en précisant si elle a sa place là. Il prend une vingtaine de secondes et ne demande aucun compte : scannez votre app.

Garder la vérification après le prochain changement

La vérification de qui est connecté est la partie d'une page la plus susceptible de disparaître quand vous demandez à votre builder de faire disparaître une erreur, et les deux solutions rapides ci-dessus la retirent.

Reeve Monitor rescanne votre app en ligne toutes les heures :

  • les neuf vérifications toutes les heures, sur trois apps au plus
  • un e-mail le jour où un nouveau scan note votre app moins bien que le précédent ; une clé secrète dans votre JavaScript plafonne à elle seule la note à D
  • si l'app répond, toutes les 60 secondes
  • un rapport mensuel de ce qui a été vu

Un serveur qui croit n'importe quel cookie, ou une fonction qui tourne avec la clé secrète, laisse un inconnu modifier les lignes de vos utilisateurs. Reeve Care garde une copie de votre base Supabase d'avant.

  • une copie chiffrée selon un calendrier, conservée sur le stockage de Reeve dans l'UE, en dehors de votre compte Supabase
  • chaque copie vérifiée avant de compter, en comptant les lignes de chaque table
  • une restauration en un clic qui sauvegarde ce qui est là 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

Que faire maintenant

Que faire

  • Lisez « Auth session missing! » comme « personne n'est connecté ici ». La requête n'a jamais quitté votre application.
  • Vérifiez si user vaut null avant de l'utiliser, et donnez à un visiteur non connecté l'écran prévu pour lui plutôt que l'erreur.
  • Sur une page de réinitialisation, attendez l'événement PASSWORD_RECOVERY avant d'afficher le formulaire, et proposez un nouveau lien s'il n'arrive jamais.
  • Dans une Edge Function ou sur un serveur, transmettez l'en-tête Authorization ou les cookies du visiteur au client que vous créez, et répondez 401 s'il n'y a toujours pas d'utilisateur.
  • Gardez getUser() sur le serveur. Là-bas, getSession() croit n'importe quel cookie qu'on lui envoie.

Si vos sessions existent puis disparaissent, si bien que des gens connectés se retrouvent soudain déconnectés, c'est un autre problème avec ses propres causes, et pourquoi les utilisateurs Supabase sont déconnectés le traite. La liste de sécurité en 10 minutes couvre ce qu'une application tout juste lancée laisse souvent ouvert par ailleurs.

FAQ

Que veut dire "Auth session missing!" dans Supabase ?

Cela veut dire que supabase-js, la bibliothèque Supabase de votre application, a été interrogée sur l'utilisateur connecté et n'avait aucune session avec laquelle poser la question. Une session, c'est ce que Supabase Auth remet à une personne quand elle se connecte, et la bibliothèque la garde dans le navigateur. S'il n'y en a pas, elle ne contacte pas Supabase du tout. Elle renvoie AuthSessionMissingError avec le statut 400 et un utilisateur à null.

Pourquoi je vois "Auth session missing!" quand personne n'est connecté ?

Parce que c'est la réponse pour un visiteur qui ne s'est pas connecté. Votre application a appelé getUser() sur une page que n'importe qui peut ouvrir, et supabase-js a signalé que personne n'était connecté. Rien n'est cassé. Votre code n'a qu'à lire l'utilisateur comme null et afficher l'écran de connexion, au lieu d'afficher l'erreur.

Pourquoi ma page de réinitialisation du mot de passe affiche "Auth session missing!" ?

Changer un mot de passe avec updateUser() demande une session, et sur une page de réinitialisation cette session doit venir du lien de l'e-mail. Si le lien avait déjà servi ou expiré, s'il a été ouvert dans un autre navigateur que celui qui l'a demandé, ou s'il a envoyé la personne vers une autre adresse, aucune session n'est arrivée et updateUser() n'a personne à modifier. Attendez l'événement PASSWORD_RECOVERY avant d'afficher le formulaire, et proposez un nouveau lien s'il ne vient jamais.

Est-ce la même chose que "invalid claim: missing sub claim" ?

Non. "Auth session missing!" est décidé dans votre application, avant qu'aucune requête n'en sorte. "invalid claim: missing sub claim" revient de Supabase Auth, qui a reçu une requête et trouvé la clé de votre projet là où devait se trouver le jeton d'une personne. Le premier veut dire qu'il n'y avait rien à envoyer. Le second, qu'on a envoyé la mauvaise chose.

Faut-il utiliser getSession() au lieu de getUser() ?

Pas pour faire disparaître cette erreur. Dans le navigateur, getSession() trouve le même stockage vide et ne renvoie aucune session, donc rien ne change sauf que le message s'arrête. Sur un serveur, c'est pire : supabase-js documente que getSession() lit la session directement dans le stockage, par exemple les cookies de la requête, qui peuvent ne pas être authentiques, et recommande getUser() à la place parce qu'il vérifie auprès de Supabase Auth.

Écrit par

Vlad Tkachenko

Fondateur de Reeve

Je passe mes journées à examiner des applications créées avec Lovable, Bolt, v0, Cursor et Replit, et la courte liste d'erreurs qui y reviennent sans cesse.

En savoir plus sur l'auteur

À lire ensuite

Tous les articles

Sauvegardez votre base Supabase avant la prochaine modification

Reeve en garde une copie chiffrée à intervalles réguliers et la restaure pour vous en cas de problème.

Voir les sauvegardes Supabase

Vérification externe automatisée, pas un audit complet. L'absence de problème n'est pas une garantie de sécurité.