Noções de segurança
Supabase "Auth session missing!": para onde foi a sessão
"Auth session missing!" é o supabase-js sem achar nenhuma sessão logada e sem chegar a perguntar ao Supabase. Para onde foi a sessão, e a correção.

Em resumo
- "Auth session missing!" (AuthSessionMissingError, status 400) vem do supabase-js, dentro do seu app. A biblioteca procurou uma sessão logada, não achou nenhuma e nunca enviou a requisição ao Supabase.
- Numa página em que ninguém fez login, essa é a resposta certa, e o seu código só precisa tratá-la. Numa página de redefinição de senha, quer dizer que o link não fez login de ninguém aqui. Numa Edge Function ou num servidor, a sessão do visitante nunca foi repassada.
- Deixe o getUser() onde está. Trocar por getSession() num servidor faz o erro sumir acreditando no que quer que o cookie diga.
O seu app do Lovable ou do Bolt faz o login das pessoas com o Supabase. Numa manhã você abre o console do navegador e encontra isto em vermelho, numa página em que nada parece errado:
AuthSessionMissingError: Auth session missing!
Ou uma cliente escreve dizendo que a página "Defina uma nova senha" recusa ela com essas mesmas palavras, toda vez.
Pesquise "auth session missing supabase" e você vai achar tópicos cheios de
correções: trocar getUser() por getSession(), voltar a versão da biblioteca,
colocar uma espera, reiniciar o projeto. Elas supõem que algo quebrou entre o seu
app e o Supabase. Nenhuma requisição foi feita. O erro nasce dentro do seu
app, escrito pela biblioteca do Supabase, antes que qualquer coisa saia do
navegador.
O que significa "Auth session missing!" no Supabase?
Perguntaram ao supabase-js quem estava logado; ele procurou uma sessão, não achou nenhuma e respondeu sozinho.
Uma sessão é o que o Supabase Auth entrega a uma pessoa quando ela faz login: um par de tokens que dizem quem ela é, guardados pelo navegador para que o próximo carregamento de página ainda a reconheça. O supabase-js é a biblioteca com que o seu app conversa com o Supabase, e toda chamada que precisa de uma pessoa começa tirando essa sessão do lugar.
A mensagem está escrita no próprio supabase-js, no arquivo errors.ts da
biblioteca de autenticação: um AuthSessionMissingError com o texto
Auth session missing! e o status 400. Esse 400 é um rótulo que a biblioteca dá
a si mesma. Nenhum servidor respondeu com ele, e os logs do Supabase não vão
mostrar nada, porque o getUser() procura uma sessão antes de fazer qualquer
outra coisa. Um comentário no código dele diz isso: devolve um erro se não houver
nem token de acesso nem um cabeçalho Authorization próprio.
Pense numa recepção que sabe dizer qual visitante é quem. Cada pessoa que faz login recebe um crachá na porta, e o supabase-js guarda esse crachá no bolso do navegador. Quando o seu código pergunta "quem é este?", o supabase-js olha primeiro no bolso. Se há um crachá, ele leva até a recepção e volta com um nome. Se o bolso está vazio, ele nem vai até a recepção. Volta na hora e diz que não há crachá.
Ele é devolvido, não lançado
O getUser() devolve o erro ao lado de um usuário vazio, e o seu código decide o
que acontece depois.
A chamada é assim, e sem sessão user fica null e error traz o
AuthSessionMissingError:
const { data: { user }, error } = await supabase.auth.getUser()
Nada é lançado, então um try … catch em volta da chamada não pega nada. É daí
que vem o estrago visível, numa de duas formas:
- O código mostra qualquer
errorque recebe. Um visitante que ainda não fez login vê "Auth session missing!" num aviso ou numa caixa vermelha, numa página que ele podia ver. - O código usa o usuário sem conferir. Ele lê
user.ide a página quebra comCannot read properties of null, o que parece um bug completamente diferente.
Os dois são o mesmo engano: tratar "ninguém está logado" como uma falha. Confira o usuário primeiro, e dê a um visitante sem login a tela feita para ele:
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
// ninguém está logado: mostrar a tela de login ou a página pública
return
}
Se você quiser diferenciar isso de uma falha de verdade, a biblioteca dá nome ao
caso: error.name é 'AuthSessionMissingError'.
Onde a sessão se perde
Em três lugares, e só o primeiro é inofensivo.
1. Ninguém fez login neste navegador. O seu app chama getUser() numa página
que qualquer pessoa pode abrir, talvez para decidir se mostra "Entrar" ou "Minha
conta". Para um visitante, o bolso vazio é a resposta verdadeira. É aqui que a
maioria encontra a mensagem, e a correção de código acima é tudo de que ela
precisa.
2. Uma página de redefinição de senha ou de link mágico. Definir uma senha
nova chama updateUser({ password }), e o
guia de senhas do Supabase é
claro quanto à ordem: é depois de ter uma sessão que se coleta a senha nova e se
chama updateUser. Nessa página, a sessão deve vir do link do e-mail, que faz o
login da pessoa ao abrir. Quando isso não acontece, o bolso está vazio e o
updateUser() responde "Auth session missing!". Os motivos de sempre:
- O link já tinha sido usado, ou tinha expirado, antes de ser aberto. Por que o Supabase diz que o link do e-mail é inválido ou expirou passa por cada um.
- Ele foi aberto num navegador ou app diferente do que o pediu. Quando um app usa o login PKCE do Supabase, parte da troca fica no navegador que a começou, e segundo a documentação do Supabase a troca do código precisa ser iniciada no mesmo navegador e dispositivo em que o fluxo começou.
- Ele mandou a pessoa para outro lugar, como
localhost, e a sessão chegou num endereço que não é o seu app. Por que o Supabase redireciona para localhost cobre esse caso.
3. O seu servidor ou uma Edge Function nunca recebeu a sessão. O crachá está
no navegador do seu visitante. Código que roda em outro lugar, uma Edge Function
do Supabase ou a metade servidor de um app Next.js feito com v0 ou Cursor, cria o
próprio cliente Supabase, e um cliente novo começa com o bolso vazio. Enquanto a
sessão do visitante não for passada para ele, o getUser() ali não encontra
nada e responde "Auth session missing!" sem perguntar ao Supabase.
Numa Edge Function a sessão viaja no cabeçalho Authorization da requisição, e o
supabase-js só a usa se você a entregar ao criar o cliente. Num app Next.js ela
viaja em cookies, que a ferramenta de servidor do Supabase só lê se você passar
os da requisição. Se uma Edge Function devolve essa falha como erro, o seu app
mostra
"Edge Function returned a non-2xx status code",
com esta mensagem esperando no log da função.
Como saber qual é o seu caso
Veja onde a mensagem aparece, e depois o que o navegador realmente enviou.
No console do navegador, numa página que qualquer pessoa pode abrir. Caso 1.
Abra as ferramentas de desenvolvedor do navegador (F12), escolha Application,
depois Local Storage e o endereço do seu app. O supabase-js guarda a sessão
sob uma chave formada por sb-, o identificador do seu projeto e -auth-token.
Se não houver uma entrada assim, este navegador não tem sessão, e a mensagem está
dizendo a verdade. Um app Next.js que usa as ferramentas de servidor do Supabase
guarda a sessão em cookies, então procure o mesmo nome em Cookies.
Numa página de redefinição ou de link mágico. Caso 2. Veja o endereço em que o link abriu. Se não for o seu app, ou se for um link que você ou um scanner de e-mail já tinha aberto, a sessão nunca teve chance de chegar.
No log de uma Edge Function, ou do seu servidor. Caso 3. Abra a aba
Network, repita a ação e clique na requisição para a sua função. Se o
cabeçalho Authorization dela tem uma sequência longa que começa com eyJ, o
navegador enviou o token de uma pessoa, e a sua função não o passou para o
cliente dela.
O que você não vai achar em nenhum caminho é uma requisição para /auth/v1/user
que voltou com esta mensagem. Se vir uma que falhou, é outro erro. Uma resposta
dizendo invalid claim: missing sub claim quer dizer que a chave do seu projeto
foi enviada onde deveria ir uma pessoa, e aquele artigo
é o gêmeo deste.
A correção para cada caso
Dê a cada um a sessão que falta, ou uma resposta que não precise dela.
Caso 1, o visitante sem login: confira user antes de usá-lo, como no código
acima, e mostre a tela feita para quem não fez login.
Caso 2, a página de redefinição: espere o link fazer o login da pessoa antes
de mostrar o formulário. O Supabase emite um evento PASSWORD_RECOVERY quando
alguém chega por um link de recuperação, e a referência dele mostra a página
esperando por ele:
supabase.auth.onAuthStateChange((event) => {
if (event === 'PASSWORD_RECOVERY') {
// o link fez o login da pessoa: agora mostrar o formulário de senha nova
}
})
Se o evento nunca vier, o link não funcionou. Diga isso e ofereça mandar um novo, em vez de mostrar um formulário que com certeza vai falhar.
Caso 3, a função ou o servidor: passe a sessão do visitante para o cliente que você cria. Numa Edge Function, crie-o com o cabeçalho que chegou:
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' },
})
}
O wrapper mais novo do Supabase, withSupabase({ auth: 'user' }), faz o mesmo
antes do seu handler rodar, e o
artigo sobre Edge Functions mostra a
configuração. Mantenha o 401 para quem chama sem sessão, para que uma requisição
de uma página sem login receba uma recusa clara. Com o cabeçalho repassado, o
getUser() ali pode devolver invalid claim: missing sub claim para quem chama
sem login: o mesmo visitante visto do outro lado, e o 401 cobre os dois.
Num app Next.js, crie o cliente de servidor com a ferramenta @supabase/ssr do
Supabase e os cookies da requisição, como mostra a configuração de servidor
dela, e chame getUser() nesse cliente.
Duas correções que pioram tudo
As duas são sugeridas para esta mensagem, e cada uma para o erro tirando a verificação que está por trás dele.
Trocar getUser() por getSession(). No navegador, isso não muda nada que
importe: o armazenamento continua vazio, então você não recebe nem sessão nem
usuário, só que sem a mensagem. Num servidor é perigoso. O guia de servidor do
Supabase diz para nunca confiar em supabase.auth.getSession() em código de
servidor, porque ele lê a sessão do cookie sem revalidar. Um cookie é algo que o
visitante manda para você, e qualquer pessoa pode mandar o cookie que quiser.
getUser() é o método que o supabase-js documenta como fazendo uma requisição ao
servidor do Supabase Auth, de modo que o resultado é autêntico. Mantenha-o em
todo lugar onde a resposta decide o que alguém pode fazer.
Colocar a chave secreta no cliente para a chamada parar de falhar. Um cliente
criado com a chave service_role ou sb_secret_ não precisa de sessão para ler
e escrever, porque ignora por completo as regras do seu banco. A função volta a
funcionar, para qualquer um que a chame, logado ou não. Se um builder faz essa
mudança em código que vai para o navegador, a chave vai junto para cada
visitante. Se isso já aconteceu, troque a chave primeiro.
Dá para conferir isso de fora. O nosso escaneamento grátis lê o JavaScript do seu app publicado como um estranho leria e nomeia cada chave que encontra, inclusive se é uma das que devem estar ali. Leva uns 20 segundos e não pede conta: escaneie o seu app.
Manter a verificação depois da próxima mudança
A verificação de quem está logado é a parte de uma página que mais facilmente some quando você pede ao seu builder para fazer um erro sumir, e as duas correções rápidas aí de cima a tiram.
O Reeve Monitor varre o seu app publicado de novo a cada hora:
- as nove verificações a cada hora, em até três apps
- um e-mail no dia em que uma nova varredura avaliar o seu app pior que a anterior; uma chave secreta no seu JavaScript limita sozinha a nota a D
- se o app está no ar, a cada 60 segundos
- um relatório mensal do que ele viu
Um servidor que acredita em qualquer cookie, ou uma função que roda com a chave secreta, deixa um estranho mudar as linhas dos seus usuários. O Reeve Care guarda uma cópia do seu banco Supabase de antes disso acontecer.
- uma cópia criptografada conforme uma agenda, guardada no armazenamento do próprio Reeve na UE, fora da sua conta Supabase
- cada cópia verificada antes de contar, contando as linhas de cada tabela
- uma restauração em um clique que salva o que está lá agora antes de substituir
- também os seus arquivos enviados, assim que você conectar uma credencial de Storage
- tudo o que o Monitor faz
O que fazer agora
O que fazer
- Leia "Auth session missing!" como "ninguém está logado aqui". A requisição nunca saiu do seu app.
- Confira se
userénullantes de usá-lo, e dê a um visitante sem login a tela feita para ele em vez do erro. - Numa página de redefinição, espere o evento
PASSWORD_RECOVERYantes de mostrar o formulário, e ofereça um link novo se ele nunca chegar. - Numa Edge Function ou num servidor, passe o cabeçalho
Authorizationou os cookies do visitante para o cliente que você cria, e responda 401 se ainda não houver usuário. - Mantenha
getUser()no servidor. Lá,getSession()acredita em qualquer cookie que receber.
Se as suas sessões existem e depois somem, e pessoas que estavam logadas de repente estão fora, é outro problema com causas próprias, e por que os usuários do Supabase são deslogados cobre isso. O checklist de segurança de 10 minutos cobre o que mais um app recém-lançado costuma deixar aberto.
Perguntas frequentes
O que significa "Auth session missing!" no Supabase?
Significa que o supabase-js, a biblioteca do Supabase dentro do seu app, foi consultado sobre o usuário logado e não tinha nenhuma sessão com que perguntar. Uma sessão é o que o Supabase Auth entrega a uma pessoa quando ela faz login, e a biblioteca a guarda no navegador. Se não há nenhuma, ela nem contata o Supabase. Ela devolve AuthSessionMissingError com o status 400 e um usuário null.
Por que vejo "Auth session missing!" quando ninguém está logado?
Porque essa é a resposta para um visitante que não fez login. O seu app chamou getUser() numa página que qualquer pessoa pode abrir, e o supabase-js informou que ninguém estava logado. Nada está quebrado. O seu código só precisa ler o usuário como null e mostrar a tela de login, em vez de mostrar o erro.
Por que a minha página de redefinição de senha diz "Auth session missing!"?
Trocar uma senha com updateUser() precisa de uma sessão, e nessa página a sessão deve vir do link do e-mail. Se o link já tinha sido usado ou tinha expirado, foi aberto num navegador diferente do que o pediu, ou mandou a pessoa para outro endereço, nenhuma sessão chegou e o updateUser() não tem ninguém para alterar. Espere o evento PASSWORD_RECOVERY antes de mostrar o formulário, e ofereça um link novo se ele nunca vier.
É a mesma coisa que "invalid claim: missing sub claim"?
Não. "Auth session missing!" é decidido dentro do seu app, antes que qualquer requisição saia dele. "invalid claim: missing sub claim" volta do Supabase Auth, que recebeu uma requisição e encontrou a chave do seu projeto onde deveria estar o token de uma pessoa. O primeiro quer dizer que não havia nada para enviar. O segundo, que foi enviada a coisa errada.
Devo usar getSession() em vez de getUser()?
Não para fazer esse erro sumir. No navegador, getSession() encontra o mesmo armazenamento vazio e não devolve nenhuma sessão, então nada muda além de a mensagem parar. Num servidor é pior: o supabase-js documenta que getSession() lê a sessão direto do armazenamento, por exemplo dos cookies da requisição, que podem não ser autênticos, e recomenda getUser() no lugar porque ele confere com o Supabase Auth.