Puedes ejecutar un sistema completo de autenticación por correo y contraseña en una aplicación de Webflow Cloud, validado en el edge en cada solicitud, sin necesidad de enviar tokens de sesión al navegador.
Supabase Auth aparece a menudo en los hilos de desarrolladores de Webflow, generalmente cuando un proyecto requiere sesiones validadas en el servidor, claims JWT personalizados o tablas de autenticación que el equipo controle directamente.
Con Webflow Cloud ejecutando tu aplicación Next.js en Cloudflare Workers, Supabase te ofrece un sistema de autenticación respaldado por PostgreSQL que tus funciones en el edge pueden validar en cada solicitud, sin enviar lógica de sesión al navegador.
En esta guía, exploramos cómo implementar la autenticación por correo/contraseña en una aplicación de Webflow Cloud: instalando @supabase/ssr, creando los clientes para el navegador y el servidor, añadiendo el proxy de sesión, creando los Route Handlers que gestionan el registro e inicio de sesión, y conectando los formularios de Webflow en el front-end.
¿Qué necesitas para configurar Supabase Auth en una aplicación de Webflow Cloud?
Necesitas un proyecto de Webflow Cloud, un proyecto de Supabase y Node.js 22 o superior. Supabase Auth funciona en el plan gratuito, por lo que no se requiere un plan de pago para seguir esta guía.
Aquí tienes la lista completa:
- Un sitio de Webflow con Webflow Cloud habilitado y una aplicación Next.js desplegada o ejecutándose localmente
- Un proyecto de Supabase (crea uno en supabase.com si aún no lo tienes)
- Node.js 22 o superior, requerido por el entorno de ejecución de Webflow Cloud
@supabase/supabase-jsy@supabase/ssr, los dos paquetes que gestionan las operaciones de Supabase y la gestión de sesiones basada en cookies
@supabase/supabase-js es isomórfico: utiliza fetch internamente. No depende de APIs específicas de Node.js, por lo que se ejecuta sin problemas en Cloudflare Workers. El paquete @supabase/ssr añade la capa de cookies necesaria para gestionar sesiones entre el servidor y el cliente sin filtrar el estado.
9 pasos para añadir Supabase Auth a tu aplicación de Webflow Cloud
@supabase/supabase-js gestiona las operaciones de Supabase, y @supabase/ssr gestiona la sesión basada en cookies en el edge. El proxy de sesión (proxy.ts) es lo que mantiene sincronizados el servidor y el navegador. Valida y actualiza el token de autenticación en cada solicitud entrante antes de que se ejecute cualquier Route Handler o Server Component.
Sigue estos pasos para poner todo en marcha.
1. Crea tu proyecto de Supabase y copia las credenciales
Dirígete al panel de control de Supabase y crea un nuevo proyecto. Asígnale un nombre, establece una contraseña para la base de datos y elige una región cercana a tus usuarios.
Una vez que el proyecto termine de aprovisionarse, abre Settings > API Keys en la barra lateral izquierda. Supabase utiliza ahora un nuevo formato de clave pública, aunque las claves anon y service_role heredadas siguen funcionando durante la transición. En Publishable key, copia el valor que comienza con sb_publishable_....
También necesitarás la Project URL que aparece en la parte superior de la misma página.
2. Instala los paquetes y configura las variables de entorno
En tu proyecto de Webflow Cloud Next.js, instala ambos paquetes:
npm install @supabase/supabase-js @supabase/ssr
Luego, crea un archivo .env.local en la raíz del proyecto con tus credenciales de Supabase:
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_your_key_here
NEXT_PUBLIC_SITE_URL=https://yoursite.webflow.io/app
NEXT_PUBLIC_SITE_URL es la URL base para la ruta de montaje de Webflow Cloud y se utiliza en la redirección de confirmación por correo electrónico. Añade los tres también a tu entorno de Webflow Cloud: abre tu proyecto Cloud en el panel de control de Webflow, navega a Environments y agrégalos en Environment Variables para que estén disponibles en las implementaciones de producción.
3. Crea los clientes de navegador y servidor
Supabase necesita dos tipos de clientes:
- Uno para los componentes de cliente (navegador)
- Uno para los controladores de ruta y componentes de servidor (servidor)
Crea una carpeta lib/supabase/ en la raíz del proyecto con dos archivos.
El cliente de navegador utiliza createBrowserClient, que internamente sigue un patrón singleton.
Puedes llamarlo tantas veces como necesites sin crear instancias duplicadas:
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}
El cliente de servidor lee y escribe cookies a través de la API cookies() de Next.js. El try/catch en setAll es intencional: los componentes de servidor no pueden escribir cookies directamente, por lo que los errores ahí son esperados y se pueden ignorar sin problemas:
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Called from a Server Component.
// The session proxy handles cookie writes on every request.
}
},
},
}
)
}
Con ambos clientes listos, tu código de navegador y de servidor pueden acceder a Supabase con el manejo de cookies adecuado para su contexto. La pieza que mantiene a ambos sincronizados es el proxy de sesión, que se ejecuta antes que todo este código y actualiza el token de autenticación en cada solicitud.
4. Añade el proxy de sesión
Next.js 16 cambió la convención middleware.ts por proxy.ts, y las aplicaciones de Webflow Cloud siguen esa convención. El archivo cumple la misma función, ejecutando lógica a nivel de solicitud antes que cualquier controlador de ruta o componente de servidor. El proxy de sesión de Supabase valida la firma JWT y actualiza el token de autenticación en cada solicitud.
Debes crearlo en la raíz del proyecto:
// proxy.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function proxy(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
)
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// getClaims() validates the JWT signature against Supabase's published
// public keys on every request. Do not substitute getSession() here.
// It trusts the cookie without revalidating the JWT.
await supabase.auth.getClaims()
return supabaseResponse
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
Vale la pena tomarse en serio el comentario en el código. <a href="https://supabase.com/docs/guides/auth/server-side/creating-a-client" target="_blank" rel="noopener noreferrer">getClaims()</a>valida la firma JWT contra las claves públicas publicadas de Supabase cada vez. getSession() no lo hace. Confía en los datos de sesión de la cookie tal cual, lo que lo hace inadecuado para comprobaciones de autenticación en el lado del servidor.
5. Crea los controladores de ruta de autenticación
Los controladores de ruta en Webflow Cloud se ejecutan en el entorno de Node.js proporcionado por el adaptador OpenNext, por lo que no necesitas añadir una exportación de entorno edge. En los controladores de ruta, pasa las cookies directamente a través de los objetos de solicitud y respuesta, no mediante next/headers.
Cada acción de autenticación tiene su propio controlador de ruta. Comienza con el registro, el inicio de sesión y el cierre de sesión:
Registro (app/api/auth/signup/route.ts):
export const runtime = 'edge'
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const { email, password } = await request.json()
const response = NextResponse.json({
message: 'Check your email to confirm your account.'
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
)
},
},
}
)
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/confirm`,
},
})
if (error) return NextResponse.json({ error: error.message }, { status: 400 })
return response
}
Inicio de sesión (app/api/auth/login/route.ts):
export const runtime = 'edge'
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const { email, password } = await request.json()
const response = NextResponse.json({ success: true })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
)
},
},
}
)
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) return NextResponse.json({ error: error.message }, { status: 401 })
return response
}
Cierre de sesión (app/api/auth/logout/route.ts):
export const runtime = 'edge'
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const response = NextResponse.json({ success: true })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
)
},
},
}
)
await supabase.auth.signOut()
return response
}
Estos tres controladores cubren el ciclo de vida básico de la sesión: crear una cuenta, iniciar sesión y cerrarla. El registro devuelve un mensaje que indica al usuario que confirme su correo electrónico, que es la ruta que añadirás a continuación.
6. Añade la ruta de confirmación de correo electrónico
Supabase envía un enlace de confirmación a los nuevos usuarios tras el registro. Ese enlace contiene parámetros token_hash y type y necesita un controlador de ruta GET para verificarlos y activar la sesión. Sin esta ruta, la confirmación por correo electrónico fallará silenciosamente.
Crea :app/auth/confirm/route.ts
// app/auth/confirm/route.ts
export const runtime = 'edge'
import { createServerClient } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { NextResponse, type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
if (!token_hash || !type) {
return NextResponse.redirect(new URL('/auth/auth-code-error', request.url))
}
const response = NextResponse.redirect(new URL(next, request.url))
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
)
},
},
}
)
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
if (error) {
return NextResponse.redirect(new URL('/auth/auth-code-error', request.url))
}
return response
}
Tras el éxito de verifyOtp , Supabase escribe una cookie de sesión y la redirección envía al usuario a la ruta next (por defecto /). Asegúrate de que NEXT_PUBLIC_SITE_URL en tus variables de entorno coincida con la ruta de montaje donde reside esta ruta. Un desajuste es la razón más común por la que los enlaces de confirmación devuelven un error 404.
7. Protege tus rutas de miembros congetClaims()
Cualquier controlador de ruta que sirva contenido exclusivo para miembros debe verificar la sesión antes de devolver datos. Llama a getClaims() al principio del controlador. Si devuelve nulo o un error, el usuario no está autenticado.
Aquí tienes el fragmento:
// app/api/member-content/route.ts
export const runtime = 'edge'
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const response = NextResponse.json({ content: null })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() { return request.cookies.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
)
},
},
}
)
const { data: { claims }, error } = await supabase.auth.getClaims()
if (error || !claims) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// claims.sub is the authenticated user's ID
return NextResponse.json({
userId: claims.sub,
content: 'Member-only content returned here'
})
}
Para proteger rutas de página completas en lugar de puntos finales de API individuales, añade lógica de redirección dentro de proxy.ts después de la llamada a getClaims() . Comprueba request.nextUrl.pathname y, si la ruta está protegida y claims es nulo, devuelve NextResponse.redirect() a tu página de inicio de sesión.
8. Conecta los formularios en tus páginas de Webflow
Desde una página de Webflow, las llamadas a fetch() en un bloque de código personalizado o un componente de cliente activan los puntos finales de autenticación. La cookie de sesión es httpOnly , por lo que el navegador la almacena automáticamente y la envía con las solicitudes posteriores, pero JavaScript no puede leerla. Esa es la ventaja de seguridad frente a los patrones de autenticación del lado del cliente.
Añade un bloque de código a tu página de inicio de sesión de Webflow con un formulario que llame al controlador de ruta de inicio de sesión:
<!-- Code Embed on your Webflow login page -->
<form id="login-form">
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Sign in</button>
<p id="error-msg" style="color:red; display:none;"></p>
</form>
<script>
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault()
const errorMsg = document.getElementById('error-msg')
errorMsg.style.display = 'none'
const formData = new FormData(e.target)
const response = await fetch('/app/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: formData.get('email'),
password: formData.get('password'),
}),
})
if (response.ok) {
window.location.href = '/members'
} else {
const { error } = await response.json()
errorMsg.textContent = error
errorMsg.style.display = 'block'
}
})
</script>
Sustituye /app/api/auth/login por tu ruta de montaje real si es diferente. El formulario de registro sigue el mismo patrón. Cambia el punto final a /app/api/auth/signup y ajusta la redirección de éxito para pedir al usuario que revise su correo electrónico en lugar de navegar inmediatamente.
9. Protege tus tablas de Supabase con seguridad a nivel de fila (RLS)
Si almacenas datos de miembros en tablas de Supabase (estado de suscripción, contenido guardado, perfiles de usuario), activa Row Level Security para evitar que un usuario acceda a las filas de otro.
En el panel de control de Supabase, abre el editor de tablas, selecciona tu tabla y activa la seguridad a nivel de fila en la configuración de la tabla.
Luego, crea políticas en el editor SQL. Para una tabla profiles donde los usuarios solo deberían acceder a su propia fila:
-- Supabase SQL Editor
CREATE POLICY "Users can view their own profile"
ON profiles
FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can update their own profile"
ON profiles
FOR UPDATE
USING (auth.uid() = user_id);
auth.uid() devuelve el ID del usuario autenticado a partir del JWT validado. Supabase lo evalúa automáticamente en cada consulta. Sin una política RLS que permita explícitamente la operación, las consultas no devuelven ninguna fila. La tabla queda bloqueada por defecto una vez que se activa RLS.
¿Qué hace que la autenticación de Supabase falle en tu aplicación Webflow Cloud?
La autenticación suele configurarse sin problemas, pero uno de estos tres problemas suele aparecer primero durante las pruebas.
Las sesiones no persisten después de iniciar sesión
Esto casi siempre significa que el controlador no está escribiendo cookies en el objeto de respuesta. Asegúrate de que tus Route Handlers creen el objeto antes que el cliente de Supabase y pasa las cookies a ese objeto exacto.setAllresponse
Si creas un después de la llamada a , las cookies se escribirán en un objeto descartado y se perderán. Construye la respuesta una vez, antes que el cliente, y escribe en ella durante todo el proceso.new NextResponse()setAll
getClaims()devuelve null inmediatamente después de iniciar sesión
Existe un breve intervalo en la primera solicitud después de iniciar sesión antes de que el token se propague a través del proxy. En los Route Handlers protegidos, trata el null como no autenticado y devuelve un 401. No recurras a . En el lado del cliente, vuelve a intentar la solicitud o redirige a la página de inicio de sesión. No omitas el 401 para ocultar el síntoma.claimsgetSession()
El enlace de confirmación de correo electrónico devuelve un 404
El enlace de confirmación llega a . Si da un error 404, comprueba dos cosas: que exista en la ruta correcta de tu proyecto y que coincida exactamente con tu ruta de montaje real, incluyendo el subdirectorio si estás montado en en lugar de en la raíz.[mount-path]/auth/confirm?token_hash=...&type=...app/auth/confirm/route.tsNEXT_PUBLIC_SITE_URL/app
Crea más con Supabase en Webflow Cloud
La capa de autenticación de esta guía es la base para cualquier función restringida a miembros. Las comprobaciones de suscripción, las preferencias guardadas, las suscripciones a bases de datos en tiempo real y el contenido generado por el usuario se basan en el mismo patrón de cookies de sesión y en la misma estructura de Route Handler.
Explora Webflow + Supabase para obtener una visión completa de lo que puedes conectar a un sitio de Webflow. Para una personalización más profunda de tu aplicación Webflow Cloud en el edge, la documentación para desarrolladores de Webflow cubre la configuración del entorno, las opciones de despliegue y el almacenamiento.
Preguntas frecuentes
¿Puedo usar proveedores de inicio de sesión social como Google o GitHub?
Sí. Supabase es compatible con Google, GitHub, Twitter y docenas de otros proveedores de OAuth. Configura el proveedor que elijas en el panel de control de Supabase en Authentication > Providers y, a continuación, llama a desde un Client Component. El mismo proxy de sesión y el patrón gestionan la actualización del token independientemente del proveedor que utilices.supabase.auth.signInWithOAuth()getClaims()
¿Webflow Cloud requiere un plan de pago?
Webflow Cloud está disponible en todos los planes de Webflow, incluido el plan gratuito Starter, con límites más altos en proyectos, almacenamiento y solicitudes en los niveles de pago. Supabase Auth es gratuito hasta 50.000 usuarios activos mensuales.
¿Cómo añado datos de perfil de usuario, como nombres de visualización y avatares?
Crea una tabla en Supabase con una columna que haga referencia a . Añade un disparador (trigger) de base de datos en el editor SQL para insertar automáticamente una fila de perfil cuando un nuevo usuario confirme su correo electrónico. Habilita RLS en la tabla con políticas SELECT y UPDATE limitadas a . El profilesuser_idauth.users(id)profilesauth.uid() = user_idtutorial de gestión de usuarios en Next.js de Supabase cubre la configuración completa del esquema y del disparador.
¿Puedo restringir contenido específico de Webflow CMS según el estado de autenticación?
Sí, y hay dos patrones que funcionan dependiendo de lo que quieras restringir. Para restringir páginas CMS completas, entrega el contenido a través de un Route Handler protegido y renderízalo en tu aplicación Cloud en lugar de en una página estática de Webflow. Para restringir elementos dentro de una página de Webflow, llama a tu endpoint protegido con en un Code Embed después de que el usuario se autentique e inyecta la respuesta en el DOM. El primer enfoque es más limpio para contenido que solo es útil para los miembros; el segundo funciona cuando la mayor parte de la página es pública, con secciones solo para miembros.fetch()





