Inicias sesión sin problema, pero cada vez que recargas la página (o al poco rato) te devuelve a la pantalla de login. Este es el bug de autenticación más común en apps hechas con vibe coding. Casi siempre es una de estas cinco causas: salta a la que coincide con tu caso.
Encuentra tu causa rápido
- A. Te expulsa en cada recarga → tu app comprueba "¿estoy logueado?" *antes* de que la librería de autenticación restaure la sesión (la más común)
- B. Se cae a los ~30–60 min → el refresco automático del token está desactivado
- C. No se guarda nunca (incógnito / cookies bloqueadas) → el almacén de la sesión (localStorage·cookies) está bloqueado
- D. Solo se rompió tras desplegar (Next.js, etc.) → falta el middleware del lado del servidor
- E. Frontend y backend en dominios distintos → mala configuración de SameSite/Secure en la cookie
A. Te expulsa al recargar — no espera a que se restaure la sesión (la más común)
Tanto Supabase como Firebase tardan un instante mínimo (asíncrono) en cargar la sesión guardada desde el almacenamiento. Si el código generado por IA ejecuta if (no user) redirect to login en el momento en que se monta la pantalla, confunde ese breve lapso de "todavía cargando" con un estado de sesión cerrada y te expulsa cada vez.
Clave: mantén un estado aparte de "todavía comprobando" y muestra un loader hasta que la autenticación se resuelva.
Firebase (v9 modular):
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 se dispara *después* de que el SDK restaure la sesión guardada: decide dentro de este callback. No leas auth.currentUser al montar (en ese instante todavía puede ser null).
Supabase:
// 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
});
En React, añade un flag loading, mantenlo en true hasta que llegue el primer callback y nunca redirijas mientras loading esté activo: solo muestra un spinner.
Prompt para tu IA:
> "Añade un estado de loading mientras se comprueba la autenticación y no redirijas a la página de login hasta que se haya ejecutado el primer callback de onAuthStateChanged / onAuthStateChange."
B. Se cae al rato — revisa el refresco automático del token
Los access tokens suelen expirar en aproximadamente una hora. Si no se refrescan automáticamente, acabas "deslogueado sin hacer nada."
Al crear el cliente de Supabase, asegúrate de que estas opciones estén activadas (en el navegador están activadas por defecto: vuelve a activarlas si las desactivaste):
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 guarda la sesión en localStorage; autoRefreshToken refresca el token antes de que expire. Un error común es pasar una configuración de auth personalizada que accidentalmente las pone en false.
Firebase usa por defecto la persistencia local, así que sobrevive al cierre del navegador y refresca los tokens por ti automáticamente.
C. No se guarda nunca — incógnito / cookies bloqueadas
La sesión vive en localStorage (o en cookies). Si ese almacén está bloqueado, se cae incluso justo después de iniciar sesión.
- Comprueba si estás en modo incógnito/privado, con cookies bloqueadas o con cookies/datos de terceros bloqueados. La persistencia
localde Firebase solo funciona "siempre que el navegador soporte este mecanismo de almacenamiento, p. ej. que las cookies/datos de terceros estén habilitados." - La prevención de rastreo de Safari (ITP) puede limitar el almacenamiento entre dominios.
- Para fijar la persistencia de Firebase de forma explícita, configúrala antes de iniciar sesión:
import { getAuth, setPersistence, browserLocalPersistence } from "firebase/auth";
const auth = getAuth();
await setPersistence(auth, browserLocalPersistence); // then call signIn
Si está configurada como browserSessionPersistence (se borra al cerrar la pestaña) o inMemoryPersistence (se borra al recargar), es normal que cierre la sesión. Cámbiala a browserLocalPersistence.
D. Deslogueado tras desplegar (Next.js) — falta middleware del servidor
Con Supabase en Next.js (App Router) haciendo renderizado en el servidor, la sesión debe vivir en cookies, no en localStorage. Pero los Server Components no pueden escribir cookies, así que, a menos que un middleware refresque el token, el token expirado se queda y te desloguea al recargar.
Solución:
- Usa
@supabase/ssr:createBrowserClienten el navegador,createServerClienten el servidor. - Añade un
middleware.tsen la raíz del proyecto que refresque la sesión en cada petición (el patrón oficialupdateSession). - En el servidor, no confíes en
getSession()para proteger páginas: usagetClaims()(ogetUser()). No se garantiza quegetSession()revalide el token.
Copia el middleware directamente de la documentación oficial de SSR de Supabase (en las fuentes de abajo): es la vía más segura.
Prompt para tu IA:
> "Divide en createBrowserClient/createServerClient de @supabase/ssr, añade un middleware.ts que ejecute updateSession en cada petición y protege las rutas del servidor con getUser."
E. Dominios distintos — SameSite/Secure de la cookie
Si tu propio backend (Express, etc.) guarda la sesión en una cookie y el frontend y la API están en dominios distintos, la cookie no se almacena/envía y te desloguea.
- Mismo dominio:
SameSite=Lax(el valor por defecto cuando no se especifica). Suele resolverlo. - Dominios distintos (cross-site): debes enviar
SameSite=None; Securejuntos. Los navegadores rechazanSameSite=NonesinSecure(HTTPS). - Por eso, probar en http localhost puede descartar la cookie
Securey romper la persistencia. Haz un proxy al mismo origen en local, o sirve por https.
Ejemplo de cookie:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/
(Usa SameSite=None; Secure entre dominios.)
SameSite=Strict puede descartar la cookie cuando llegas desde otro sitio (p. ej. al volver de un proveedor de OAuth), rebotándote al login. Lax es la respuesta correcta en la mayoría de los casos.
Sigues atascado — cuando no logras identificar la causa
Aquí no hay una solución universal de una línea; la causa depende de tu stack. Acótalo en orden:
- Abre DevTools → Application → Local Storage / Cookies y confirma que el valor de la sesión realmente se guarda tras iniciar sesión. No se guarda → C (almacén bloqueado) o E (configuración de la cookie).
- Se guarda pero desaparece al recargar → A (timing de restauración) o D (middleware SSR).
- Solo se cae al rato → B (refresco del token).
- Si nada de esto ayuda, pega el código de ejemplo oficial de las fuentes de abajo y consigue primero una reproducción mínima que funcione.
