Você entra sem problema, mas cada vez que atualiza a página (ou depois de um tempinho) é jogado de volta para a tela de login. Esse é o bug de autenticação mais comum em apps feitos com vibe coding. Quase sempre é uma destas cinco causas: pule para a que combina com o seu caso.

Descubra sua causa rápido

  • A. Expulso a cada atualização → seu app verifica "estou logado?" *antes* de a biblioteca de autenticação restaurar a sessão (a mais comum)
  • B. Cai depois de ~30–60 min → a renovação automática do token está desativada
  • C. Nunca é salvo (anônimo / cookies bloqueados) → o armazenamento da sessão (localStorage·cookies) está bloqueado
  • D. Só quebrou depois do deploy (Next.js etc.) → falta o middleware do lado do servidor
  • E. Frontend e backend em domínios diferentes → configuração errada de SameSite/Secure no cookie

A. Expulso ao atualizar — não espera a sessão ser restaurada (a mais comum)

Tanto o Supabase quanto o Firebase levam um instante mínimo (assíncrono) para carregar o login salvo do armazenamento. Se o código gerado por IA executa if (no user) redirect to login no exato momento em que a tela é montada, ele confunde esse breve intervalo de "ainda carregando" com um estado deslogado e te expulsa toda vez.

Chave: mantenha um estado separado de "ainda verificando" e mostre um loader até a autenticação ser resolvida.

Firebase (v9 modular):

js

import { getAuth, onAuthStateChanged } from "firebase/auth";

const auth = getAuth();
onAuthStateChanged(auth, (user) => {
  if (user) {
    // logged in — render the app
  } else {
    // only redirect to login when truly signed out
  }
});

O onAuthStateChanged dispara *depois* que o SDK restaura a sessão salva — decida dentro desse callback. Não leia auth.currentUser na montagem (nesse instante ainda pode ser null).

Supabase:

js

// once on load: read the saved session
const { data: { session } } = await supabase.auth.getSession();

// then subscribe to changes
supabase.auth.onAuthStateChange((event, session) => {
  // session present = logged in
});

No React, adicione uma flag loading, mantenha em true até chegar o primeiro callback e nunca redirecione enquanto loading estiver ativo — apenas mostre um spinner.

Prompt para a sua IA:

> "Adicione um estado de loading enquanto a autenticação está sendo verificada e não redirecione para a página de login até o primeiro callback de onAuthStateChanged / onAuthStateChange ter sido executado."

B. Cai depois de um tempo — verifique a renovação automática do token

Os access tokens costumam expirar em cerca de uma hora. Se não forem renovados automaticamente, você fica "deslogado sem fazer nada."

Ao criar o cliente do Supabase, garanta que estas opções estejam ativadas (no navegador elas vêm ativadas por padrão — reative-as se você as desativou):

js

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
  auth: {
    persistSession: true,     // save session to localStorage
    autoRefreshToken: true,   // refresh token before it expires
    detectSessionInUrl: true, // detect the session in the URL after OAuth
  },
})

persistSession salva a sessão no localStorage; autoRefreshToken renova o token antes de ele expirar. Um erro comum é passar uma configuração de auth personalizada que acidentalmente deixa essas opções como false.

O Firebase usa por padrão a persistência local, então ela sobrevive ao fechamento do navegador e renova os tokens para você automaticamente.

C. Nunca é salvo — anônimo / cookies bloqueados

A sessão fica no localStorage (ou em cookies). Se esse armazenamento estiver bloqueado, ela cai mesmo logo após o login.

  • Verifique se está em modo anônimo/privativo, com cookies bloqueados ou com cookies/dados de terceiros bloqueados. A persistência local do Firebase só funciona "desde que o navegador suporte esse mecanismo de armazenamento, por exemplo, com cookies/dados de terceiros habilitados."
  • A prevenção de rastreamento do Safari (ITP) pode limitar o armazenamento entre domínios.
  • Para fixar a persistência do Firebase explicitamente, defina-a antes de fazer login:
js

import { getAuth, setPersistence, browserLocalPersistence } from "firebase/auth";

const auth = getAuth();
await setPersistence(auth, browserLocalPersistence); // then call signIn

Se estiver definida como browserSessionPersistence (limpa ao fechar a aba) ou inMemoryPersistence (limpa ao atualizar), é esperado que deslogue. Mude para browserLocalPersistence.

D. Deslogado depois do deploy (Next.js) — falta middleware no servidor

Com o Supabase no Next.js (App Router) fazendo renderização no servidor, a sessão precisa ficar em cookies, não no localStorage. Mas os Server Components não conseguem escrever cookies, então, a menos que um middleware renove o token, o token expirado permanece e você é deslogado ao atualizar.

Correção:

  1. Use @supabase/ssr: createBrowserClient no navegador, createServerClient no servidor.
  2. Adicione um middleware.ts na raiz do projeto que renove a sessão a cada requisição (o padrão oficial updateSession).
  3. No servidor, não confie no getSession() para proteger páginas — use getClaims() (ou getUser()). Não há garantia de que o getSession() revalide o token.

Copie o middleware direto da documentação oficial de SSR do Supabase (nas fontes abaixo) — é o caminho mais seguro.

Prompt para a sua IA:

> "Separe em createBrowserClient/createServerClient do @supabase/ssr, adicione um middleware.ts que rode updateSession a cada requisição e proteja as rotas do servidor com getUser."

E. Domínios diferentes — SameSite/Secure do cookie

Se o seu próprio backend (Express, etc.) guarda o login em um cookie e o frontend e a API estão em domínios diferentes, o cookie não é armazenado/enviado e você é deslogado.

  • Mesmo domínio: SameSite=Lax (o padrão quando não especificado). Costuma resolver.
  • Domínios diferentes (cross-site): você precisa enviar SameSite=None; Secure juntos. Os navegadores rejeitam SameSite=None sem Secure (HTTPS).
  • Por isso, testar em http localhost pode descartar o cookie Secure e quebrar a persistência. Faça um proxy para a mesma origem localmente, ou sirva por https.

Exemplo de cookie:

text

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/

(Use SameSite=None; Secure entre domínios.)

SameSite=Strict pode descartar o cookie quando você chega de outro site (por exemplo, ao voltar de um provedor de OAuth), te jogando de volta para o login. Lax é a resposta certa na maioria dos casos.

Ainda travado — quando você não consegue identificar a causa

Aqui não existe uma solução universal de uma linha; a causa depende do seu stack. Vá restringindo na ordem:

  1. Abra o DevTools → Application → Local Storage / Cookies e confirme que o valor da sessão é realmente salvo após o login. Não salvo → C (armazenamento bloqueado) ou E (configuração do cookie).
  2. Salvo, mas some ao atualizar → A (timing da restauração) ou D (middleware SSR).
  3. Só cai depois de um tempo → B (renovação do token).
  4. Se nada disso ajudar, cole o código de exemplo oficial das fontes abaixo e faça primeiro uma reprodução mínima funcionar.