¿Problemas con Better Auth Cookies en Next.js? Hay Fix

Better Auth cookies en Next.js fallan en producción cuando Cloudflare actúa como proxy reverso: el prefijo __Secure- genera un mismatch entre la cookie que el servidor setea y la que el navegador espera. El resultado es que la sesión no persiste, el usuario queda deslogueado, y no hay error visible en consola. La solución pasa por configurar correctamente baseURL, useSecureCookies y trustedOrigins en Better Auth.

En 30 segundos

  • Better Auth agrega automáticamente el prefijo __Secure- a las cookies cuando detecta HTTPS, pero detrás de Cloudflare el origen recibe HTTP, generando un mismatch que rompe la sesión
  • El problema afecta a Next.js desplegado en Cloudflare Pages, Workers, y cualquier setup donde Cloudflare termine SSL antes del servidor de origen
  • La solución más directa: configurar baseURL con https:// explícito y forzar advanced.useSecureCookies: true en la config de Better Auth
  • En Next.js middleware, hay que chequear ambas variantes de cookie (__Secure-better-auth.session_token y better-auth.session_token) porque getSessionCookie no siempre detecta el protocolo correcto
  • Better Auth 1.5 incluyó fixes parciales para headers inmutables en Workers, pero el problema de detección de protocolo detrás de proxy sigue requiriendo configuración manual

Better Auth es una librería de autenticación para aplicaciones TypeScript que gestiona sesiones mediante cookies. Cuando desplegás una app Next.js detrás de Cloudflare —ya sea en Pages, Workers, o simplemente usando Cloudflare como CDN— las cookies de sesión pueden dejar de funcionar sin ningún mensaje de error obvio. El problema de fondo es una discrepancia entre el protocolo que Better Auth detecta en el servidor y el que el navegador del usuario realmente usa.

Qué es el prefijo __Secure- en cookies y por qué existe

Los prefijos de cookies son un mecanismo de seguridad definido en el RFC 6265bis que los navegadores modernos implementan desde hace años. Cuando una cookie tiene el nombre que empieza con __Secure-, el navegador aplica una regla estricta: solo acepta esa cookie si fue enviada con el atributo Secure sobre una conexión HTTPS. Si alguna de esas dos condiciones falla, el navegador descarta la cookie silenciosamente. No hay error en consola, no hay warning. Simplemente no se guarda.

Existe también el prefijo __Host-, que es todavía más restrictivo: además de requerir HTTPS y Secure, exige que no haya atributo Domain y que Path sea /. Better Auth usa __Secure- por defecto en producción, que es el balance correcto entre seguridad y flexibilidad.

El objetivo de estos prefijos es prevenir ataques donde un script malicioso en un subdominio o una conexión HTTP insegura sobreescriba cookies de sesión legítimas. Son una capa de defensa del lado del navegador. El problema es que esta capa asume que el servidor sabe si está operando sobre HTTPS o no. Y detrás de un proxy reverso como Cloudflare, esa asunción se rompe.

Cómo Better Auth gestiona las cookies de sesión

Better Auth nombra sus cookies con el formato {prefix}.{cookie_name}. El prefijo por defecto es better-auth, así que la cookie de sesión termina siendo better-auth.session_token. Hasta acá, ningún misterio.

Cuando Better Auth detecta que la aplicación corre sobre HTTPS —ya sea porque baseURL empieza con https:// o porque advanced.useSecureCookies está en true— agrega el prefijo del navegador __Secure- al nombre. La cookie pasa a llamarse __Secure-better-auth.session_token. Esto activa la protección del navegador que mencioné antes.

El tema es que internamente Better Auth tiene dos funciones que manejan cookies de forma diferente. La función createCookie —usada al generar la respuesta de autenticación— respeta correctamente la configuración de useSecureCookies y baseURL para decidir si agregar el prefijo. Pero getSessionCookie, que se usa típicamente en el middleware de Next.js para verificar si hay sesión, no siempre hace la misma detección. Depende del request entrante para inferir el protocolo, y ahí es donde se produce la inconsistencia.

En desarrollo local (HTTP en localhost), todo funciona perfecto: no se agrega __Secure-, la cookie se llama better-auth.session_token, el navegador la acepta. En producción con HTTPS directo (sin proxy), también funciona: se agrega __Secure-, todo coincide. El problema aparece exclusivamente cuando hay un intermediario que modifica el protocolo entre el usuario y el servidor.

El problema: mismatch de protocolo detrás de Cloudflare

Cloudflare actúa como terminador SSL. El usuario accede a https://tuapp.com, Cloudflare recibe esa conexión HTTPS, y luego reenvía el tráfico al servidor de origen por HTTP (o HTTPS, según tu configuración de SSL). En el modo “Flexible” —que es el default de muchos planes— el tráfico entre Cloudflare y tu origen va por HTTP plano.

Cuando el request llega a tu servidor Next.js, Better Auth inspecciona la conexión y ve HTTP. Sin el header x-forwarded-proto: https o sin un baseURL explícito con https://, Better Auth concluye que la app está en HTTP y no agrega el prefijo __Secure-. Setea la cookie como better-auth.session_token.

Pero hay un escenario todavía más confuso. Si configuraste baseURL con https://, Better Auth sí agrega el prefijo __Secure- al crear la cookie de sesión. La cookie viaja en el Set-Cookie header de vuelta al navegador a través de Cloudflare. El navegador la recibe por HTTPS (porque Cloudflare sirve HTTPS al usuario), la acepta, y todo parece funcionar. Pero cuando el middleware de Next.js intenta leer la sesión con getSessionCookie, busca la cookie sin el prefijo __Secure- porque detecta HTTP en el request entrante. Resultado: la sesión “no existe” para el middleware aunque el navegador la tiene guardada correctamente. Si te interesa, podés leer más sobre buenas prácticas de seguridad en GitHub.

El síntoma clásico: el usuario se loguea, la página de login dice “éxito”, pero al redirigir a una ruta protegida, el middleware lo devuelve al login. Un loop infinito de autenticación sin ningún error visible.

Escenarios afectados: Workers, Pages y proxy reverso

No todos los despliegues detrás de Cloudflare fallan de la misma manera. Hay matices importantes según la arquitectura.

Next.js en Cloudflare Pages con API en Workers

Este es el setup donde el frontend Next.js corre en Cloudflare Pages y la autenticación vive en un Worker separado (por ejemplo, con Hono). Las cookies de sesión se setean desde el Worker, pero el dominio puede diferir. Según el issue #7657 reportado en GitHub, en Better Auth v1.4.x el cross-origin auth se rompió porque las cookies no se propagaban correctamente entre dominios cuando ambos estaban detrás de Cloudflare. El header Set-Cookie del Worker no incluía los atributos SameSite y Domain necesarios para que el navegador las aceptara en el contexto del frontend.

Next.js full-stack en Cloudflare Pages

Cuando desplegás Next.js completo en Cloudflare Pages (frontend + API routes), el runtime es Workers-based. El problema acá es doble: primero, el mismatch de protocolo que ya describí. Segundo, los headers en el entorno Workers son inmutables por defecto —no podés modificar un Response después de crearlo—, lo que causaba errores al intentar setear cookies de sesión. Este segundo problema se corrigió en Better Auth 1.5, pero el primero persiste si no configurás la detección de protocolo manualmente.

Cloudflare como CDN/proxy delante de Vercel u otro hosting

El escenario más común y el más fácil de resolver. Tu Next.js corre en Vercel, Render, un VPS, o cualquier otro hosting, y Cloudflare está adelante como CDN. Acá el problema es puramente de detección de protocolo. Cloudflare envía el header CF-Visitor con el scheme original y también x-forwarded-proto, pero Better Auth no los lee automáticamente en todas las versiones. La solución es explícita: forzar baseURL y useSecureCookies.

Diagnóstico paso a paso del error de Better Auth cookies en Next.js

Antes de tocar la configuración, confirmá que el problema es efectivamente el mismatch de prefijo. Estos son los pasos concretos para diagnosticarlo.

Primero, abrí DevTools en el navegador, andá a la pestaña Application (Chrome) o Storage (Firefox), y mirá la sección Cookies para tu dominio. Buscá dos cosas: si la cookie se llama __Secure-better-auth.session_token o better-auth.session_token. Si no aparece ninguna de las dos después de loguearte, el problema es que el navegador la rechazó al recibirla —probablemente porque tiene prefijo __Secure- pero llegó sin atributo Secure o por HTTP.

Segundo, revisá los Response Headers de la request de login (la que llama a la API de Better Auth). Buscá el header Set-Cookie. Fijate si el nombre de la cookie tiene el prefijo __Secure- y si incluye los atributos Secure y SameSite. Si el prefijo está pero Secure no, hay un bug en tu versión de Better Auth.

Tercero, chequeá qué headers recibe tu servidor. Podés agregar un log temporal en tu API route o middleware:

console.log('Proto:', req.headers.get('x-forwarded-proto'));
console.log('CF-Visitor:', req.headers.get('cf-visitor'));

Si x-forwarded-proto es http o está ausente, ese es tu problema. Better Auth no puede saber que el usuario accedió por HTTPS.

Cuarto, verificá tu configuración de Better Auth. Abrí el archivo donde definís betterAuth() y chequeá que baseURL empiece con https://. Si tenés BETTER_AUTH_URL en tus variables de entorno, confirmá que también use https://. Si te interesa, podés leer más sobre vulnerabilidades comunes en paquetes npm.

Soluciones y configuración correcta

Hay varias soluciones, y en la mayoría de los casos necesitás aplicar más de una. Acá van en orden de prioridad.

Forzar baseURL con HTTPS

Esta es la solución más importante. En tu configuración de Better Auth, asegurate de que baseURL use https:// explícitamente:

export const auth = betterAuth({
  baseURL: "https://tuapp.com",
  // ... resto de config
});

También podés usar la variable de entorno BETTER_AUTH_URL=https://tuapp.com. Better Auth la lee automáticamente. El punto clave: no uses process.env.NEXT_PUBLIC_URL ni ninguna variable que pueda resolver a HTTP en el servidor.

Activar useSecureCookies

Forzá el flag de cookies seguras independientemente de la detección automática:

export const auth = betterAuth({
  baseURL: "https://tuapp.com",
  advanced: {
    useSecureCookies: true,
  },
});

Esto le dice a Better Auth: “siempre usá el prefijo __Secure-, no intentes detectar el protocolo”. Combinado con el baseURL correcto, resuelve el 90% de los casos.

Configurar trustedOrigins

Si tu frontend y backend están en dominios distintos (por ejemplo, frontend en tuapp.com y auth API en api.tuapp.com), necesitás declarar ambos como orígenes confiables:

export const auth = betterAuth({
  trustedOrigins: ["https://tuapp.com", "https://api.tuapp.com"],
  // ...
});

Sin esto, Better Auth puede rechazar requests cross-origin legítimos por política de CORS, y las cookies no se setean.

Chequear ambas cookies en middleware de Next.js

Mientras el bug de getSessionCookie persista, la solución pragmática en el middleware es verificar ambas variantes: Si te interesa, podés leer más sobre gestión de políticas en Azure AD.

export function middleware(request) {
  const session = request.cookies.get("__Secure-better-auth.session_token")
    || request.cookies.get("better-auth.session_token");
  if (!session) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

Eso sí: getSessionCookie solo verifica que la cookie exista. No valida la sesión contra el servidor. Para rutas sensibles, siempre validá la sesión con una llamada a la API de Better Auth cerca del dato protegido, no solo en el edge middleware.

Usar el plugin nextCookies

Better Auth ofrece un plugin específico para Next.js que maneja las cookies correctamente en el contexto de los Server Components y Server Actions:

import { nextCookies } from "better-auth/next-js";

export const auth = betterAuth({
  plugins: [nextCookies()],
  // ...
});

Este plugin usa la API de cookies de Next.js (cookies() de next/headers) para leer y escribir cookies, evitando las inconsistencias del manejo manual de headers.

Cambios relevantes en Better Auth 1.5

La versión 1.5 de Better Auth, lanzada a principios de 2026, trajo varias correcciones directamente relacionadas con este problema. La más significativa: el fix de headers inmutables en Cloudflare Workers. Antes de 1.5, intentar setear una cookie en un Worker tiraba un error de runtime porque los headers del Response no se podían modificar después de creados. La librería ahora clona el response antes de agregar headers, evitando ese crash.

También se mejoró la detección de entorno seguro con fallbacks más robustos. Better Auth 1.5 ahora chequea, en orden: x-forwarded-proto, el header CF-Visitor de Cloudflare, la propiedad url del request, y finalmente la configuración explícita de baseURL. En versiones anteriores solo miraba el protocolo del request entrante, lo cual fallaba detrás de cualquier proxy.

Ahora bien, la actualización a 1.5 no es una solución mágica. Si tu Cloudflare está en modo SSL “Flexible” (tráfico al origen por HTTP) y no pasás x-forwarded-proto, Better Auth 1.5 sigue dependiendo de que configures baseURL con https://. Los fallbacks ayudan, pero la configuración explícita sigue siendo la recomendación oficial. Mi consejo: actualizá a 1.5 como mínimo, pero no te saltees la configuración manual.

AspectoAntes de Better Auth 1.5Better Auth 1.5+
Headers en WorkersError por headers inmutablesClona response antes de modificar
Detección de protocoloSolo mira request.urlFallback: x-forwarded-proto → CF-Visitor → baseURL
Cookie prefix en proxyRequiere config manual obligatoriaMejor detección automática, pero config manual recomendada
trustedOrigins wildcardsNo soportadoSoporta patrones con wildcard (*.tuapp.com)
Cross-origin cookiesBugs en v1.4.x con SameSiteCorregido: SameSite=None para cross-origin
better auth cookies next.js diagrama explicativo

Buenas prácticas para autenticación en edge con Next.js

El mismatch de cookies es un síntoma de un problema más amplio: la autenticación en el edge tiene limitaciones que muchos equipos descubren en producción.

No dependas exclusivamente de getSessionCookie en el middleware de Next.js. Esta función solo verifica que la cookie exista —no valida la sesión contra la base de datos ni chequea si expiró—. Es útil para redirigir usuarios no logueados, pero no reemplaza una validación real. Siempre validá la sesión completa cerca del recurso protegido: en la Server Action, en el API route, o en el Server Component que carga los datos sensibles. Si te interesa, podés leer más sobre migrar a una plataforma propia.

Un ejemplo concreto: imaginá que tenés una app de gestión de proyectos desplegada en Vercel con Cloudflare adelante. Tu middleware en middleware.ts chequea la cookie y redirige al login si no existe. Pero un atacante podría crear una cookie falsa con el nombre correcto. Si tu Server Component que carga los datos del proyecto no valida la sesión contra Better Auth, los datos quedan expuestos. La regla es: el middleware es un filtro rápido, no un guardián de seguridad.

Safari agrega otra capa de complejidad. Intelligent Tracking Prevention (ITP) en Safari restringe cookies de terceros agresivamente. Si tu API de autenticación está en un dominio diferente al frontend, Safari puede bloquear las cookies de sesión. La solución: usá un proxy en el mismo dominio. En Next.js, esto significa rutear las llamadas de auth a través de tus propios API routes en vez de llamar directamente a un dominio externo.

Otro caso real que aparece seguido: equipos que usan Next.js 14+ con el App Router y despliegan en un VPS con Cloudflare adelante. Configuro la app con Donweb en un VPS con Ubuntu, SSL “Full (Strict)” en Cloudflare, baseURL con HTTPS, y useSecureCookies: true. Cero problemas con cookies. El modo SSL de Cloudflare es clave: “Full (Strict)” fuerza HTTPS entre Cloudflare y el origen, así que x-forwarded-proto dice https y Better Auth detecta todo correctamente.

Qué está confirmado y qué todavía no está confirmado

Confirmado

  • Better Auth agrega el prefijo __Secure- automáticamente cuando baseURL usa HTTPS o useSecureCookies es true, según la documentación oficial de cookies
  • El mismatch de prefijo entre createCookie y getSessionCookie es un problema documentado en múltiples issues de GitHub (#3119, #7156, #7657)
  • Better Auth 1.5 corrigió el error de headers inmutables en Cloudflare Workers
  • La configuración de baseURL con HTTPS explícito y useSecureCookies: true resuelve el problema en la mayoría de los escenarios
  • El plugin nextCookies es la integración recomendada para Next.js

Todavía no confirmado

  • No queda claro si Better Auth planea hacer la detección de x-forwarded-proto automática en todas las funciones internas, no solo en createCookie
  • La discusión sobre unificar el comportamiento de getSessionCookie con createCookie sigue abierta —no hay timeline para un fix definitivo
  • El soporte completo para Cloudflare Workers como runtime principal (no solo como proxy) todavía tiene edge cases sin resolver, especialmente con D1 como base de datos de sesiones

Errores comunes

Asumir que el problema es CORS

Cuando la sesión no persiste después del login, el primer instinto es revisar CORS. Y sí, CORS puede ser parte del problema si tu frontend y backend están en dominios distintos. Pero el mismatch de cookies de Better Auth no es un error de CORS. Los síntomas son parecidos —la request “funciona” pero la sesión no se guarda— pero la causa es otra. Antes de agregar headers CORS permisivos a tu servidor, chequeá las cookies en DevTools. Si la cookie aparece con el nombre correcto y los atributos Secure y SameSite adecuados, entonces sí puede ser CORS. Si la cookie no aparece o tiene el nombre equivocado, es el mismatch de prefijo.

Usar useSecureCookies sin baseURL

Activar advanced.useSecureCookies: true sin configurar baseURL puede empeorar las cosas. Better Auth va a agregar el prefijo __Secure- al crear la cookie, pero si baseURL no está definido, otras partes de la librería (como la generación de URLs de callback) pueden generar links con HTTP, creando inconsistencias adicionales. Siempre configurá ambos juntos. No es uno u otro.

Confiar en el middleware para validar sesiones

El middleware de Next.js corre en el edge, con acceso limitado a APIs de Node.js. getSessionCookie de Better Auth solo chequea la presencia de la cookie, no la valida criptográficamente ni contra la base de datos. Muchos equipos configuran todo el control de acceso en el middleware y después se sorprenden cuando un usuario con una cookie expirada o falsa accede a datos protegidos. El middleware es un filtro de UX (redirigir al login), no una barrera de seguridad. La validación real va en el Server Component o API route que accede al dato sensible.

Preguntas Frecuentes

¿Por qué Better Auth no guarda la sesión en producción con Cloudflare?

Cloudflare termina SSL y reenvía tráfico HTTP al origen. Better Auth detecta HTTP en el servidor, pero la cookie necesita el prefijo __Secure- porque el usuario accedió por HTTPS. El navegador descarta la cookie silenciosamente por el mismatch. La solución: configurar baseURL con https:// explícito y advanced.useSecureCookies: true.

¿Cómo configurar cookies seguras en Better Auth detrás de un proxy reverso?

Necesitás tres cosas: baseURL con https://, advanced.useSecureCookies: true, y verificar que tu proxy pase el header x-forwarded-proto. En Cloudflare, activar SSL modo “Full” o “Full (Strict)” asegura que x-forwarded-proto sea https. También conviene agregar trustedOrigins con todos los dominios involucrados.

¿Qué configuración necesita Better Auth para funcionar en Cloudflare Workers?

Actualizá a Better Auth 1.5 como mínimo para evitar el error de headers inmutables. Configurá baseURL con HTTPS, activá useSecureCookies, y usá el plugin nextCookies si estás con Next.js. Si usás D1 como base de datos, verificá la compatibilidad del adaptador de sesiones porque hay edge cases documentados en las discusiones del repositorio.

¿Cómo saber si el problema es el prefijo __Secure- o es otra cosa?

Abrí DevTools > Application > Cookies y buscá la cookie de Better Auth después de loguearte. Si no aparece ninguna cookie, el navegador la rechazó (problema de prefijo o atributo Secure). Si aparece pero con el nombre sin __Secure-, el middleware puede estar buscando la variante equivocada. Si aparece correctamente pero la sesión no funciona, el problema está en la validación del server-side, no en las cookies.

Conclusión

El mismatch de cookies de Better Auth detrás de Cloudflare es un problema que combina tres capas: un estándar de seguridad del navegador (prefijos __Secure-), un proxy que modifica el protocolo (Cloudflare terminando SSL), y una librería que no siempre detecta el protocolo correcto internamente (inconsistencia entre createCookie y getSessionCookie).

La receta que funciona: actualizá a Better Auth 1.5+, configurá baseURL con HTTPS explícito, activá useSecureCookies: true, declaré trustedOrigins, y usá el plugin nextCookies. En el middleware, chequeá ambas variantes de cookie hasta que el equipo de Better Auth unifique el comportamiento. Y no confíes en el middleware como única capa de seguridad —validá la sesión cerca del dato sensible, siempre.

Si estás evaluando Better Auth para un proyecto nuevo con Cloudflare, la librería funciona bien una vez que la configurás correctamente. El problema no es que no funcione, es que la configuración por defecto asume que el servidor conoce su propio protocolo, algo que detrás de un proxy no es cierto. Documentar estos pasos en tu README del proyecto te va a ahorrar horas la próxima vez que alguien del equipo despliegue a producción.

Fuentes

Te puede interesar...