You sign in fine, but every page refresh (or after a short while) throws you back to the login screen. This is the single most common auth bug in vibe-coded apps. It's almost always one of these five causes — jump to the one that matches you.

Find your cause fast

  • A. Kicked out on every refresh → your app checks "am I logged in?" *before* the auth library restores the session (most common)
  • B. Drops after ~30–60 min → token auto-refresh is off
  • C. Never saved at all (incognito / blocked cookies) → the session store (localStorage·cookies) is blocked
  • D. Only broke after deploying (Next.js etc.) → missing server-side middleware
  • E. Frontend and backend on different domains → cookie SameSite/Secure misconfig

A. Kicked out on refresh — not waiting for session restore (most common)

Both Supabase and Firebase take a tiny moment (async) to load the saved login from storage. If AI-generated code runs if (no user) redirect to login the instant the screen mounts, it mistakes that brief "still loading" gap for a logged-out state and bounces you every time.

Key: keep a separate "still checking" state and show a loader until auth resolves.

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
  }
});

onAuthStateChanged fires *after* the SDK restores the saved session — decide inside this callback. Don't read auth.currentUser on mount (it can still be null at that instant).

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
});

In React, add a loading flag, keep it true until the first callback arrives, and never redirect while loading — just show a spinner.

Prompt for your AI:

> "Add a loading state while auth is being checked, and don't redirect to the login page until the first onAuthStateChanged / onAuthStateChange callback has run."

B. Drops after a while — check token auto-refresh

Access tokens usually expire in about an hour. If they aren't auto-refreshed, you get "logged out while doing nothing."

When creating the Supabase client, make sure these are on (they're on by default in the browser — turn them back on if you disabled them):

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 saves the session to localStorage; autoRefreshToken refreshes the token before it expires. A common mistake is passing a custom auth config that accidentally sets these to false.

Firebase defaults to local persistence, so it survives a browser close and refreshes tokens for you automatically.

C. Never saved — incognito / blocked cookies

The session lives in localStorage (or cookies). If that store is blocked, you drop even right after login.

  • Check for incognito/private mode, blocked cookies, or blocked third-party cookies/data. Firebase local persistence only works "provided the browser supports this storage mechanism, e.g. 3rd party cookies/data are enabled."
  • Safari's tracking prevention (ITP) can limit cross-domain storage.
  • To pin Firebase persistence explicitly, set it before signing in:
js

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

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

If it's set to browserSessionPersistence (cleared when the tab closes) or inMemoryPersistence (cleared on refresh), logout is expected. Switch to browserLocalPersistence.

D. Logged out after deploying (Next.js) — missing server middleware

With Supabase in Next.js (App Router) doing server rendering, the session must live in cookies, not localStorage. But Server Components can't write cookies, so unless middleware refreshes the token, the expired token stays and you're logged out on refresh.

Fix:

  1. Use @supabase/ssr: createBrowserClient in the browser, createServerClient on the server.
  2. Add a middleware.ts at the project root that refreshes the session on every request (the official updateSession pattern).
  3. On the server, don't trust getSession() to guard pages — use getClaims() (or getUser()). getSession() isn't guaranteed to revalidate the token.

Copy the middleware straight from Supabase's official SSR docs (in sources below) — it's the safest route.

Prompt for your AI:

> "Split into createBrowserClient/createServerClient from @supabase/ssr, add middleware.ts that runs updateSession on every request, and guard server routes with getUser."

E. Different domains — cookie SameSite/Secure

If your own backend (Express, etc.) keeps the login in a cookie and the frontend and API are on different domains, the cookie won't be stored/sent and you get logged out.

  • Same domain: SameSite=Lax (the default when unspecified). Usually solves it.
  • Different domains (cross-site): you must send SameSite=None; Secure together. Browsers reject SameSite=None without Secure (HTTPS).
  • So testing on http localhost can drop the Secure cookie and break persistence. Proxy to the same origin locally, or serve over https.

Example cookie:

text

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

(Use SameSite=None; Secure across domains.)

SameSite=Strict can drop the cookie when you arrive from another site (e.g. returning from an OAuth provider), bouncing you to login. Lax is the right answer in most cases.

Still stuck — when you can't pin the cause

There's no one-line universal fix here; the cause depends on your stack. Narrow it down in order:

  1. Open DevTools → Application → Local Storage / Cookies and confirm the session value is actually saved after login. Not saved → C (blocked store) or E (cookie config).
  2. Saved but gone on refresh → A (restore timing) or D (SSR middleware).
  3. Only drops after a while → B (token refresh).
  4. If none of it helps, paste the official example code from the sources below and get a minimal reproduction working first.