Cómo crear un portal seguro con acceso por roles en Webflow Cloud usando Clerk y Next.js

Cómo crear un portal seguro con acceso restringido por roles en Webflow Cloud usando Clerk y Next.js

Aprende a añadir la autenticación de Clerk y el control de acceso basado en roles a una Webflow Cloud App.

Cómo crear un portal seguro con acceso restringido por roles en Webflow Cloud usando Clerk y Next.js

Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Índice de contenidos

Los portales de cliente donde los administradores y los clientes ven paneles diferentes son de lo más común que crean las agencias en Webflow, y desde que las Cuentas de Usuario desaparecieron en enero de 2026, su arquitectura ha cambiado por completo.

Las Cuentas de Usuario de Webflow finalizaron el 29 de enero de 2026. Para los desarrolladores que crean portales con acceso por roles, donde los clientes ven su panel, los administradores el suyo y los usuarios no autenticados llegan a la página de inicio de sesión, el reemplazo recomendado es una Webflow Cloud App con Clerk gestionando la autenticación.

Obtendrás rutas protegidas por roles, tokens de sesión, un panel de Clerk para gestionar usuarios, y todo ello alojado en tu dominio de Webflow.

Algo que vale la pena señalar antes de empezar: el middleware de Clerk protege las rutas en el borde (edge), pero eso por sí solo no es suficiente. Una vulnerabilidad crítica (CVE-2025-29927) demostró que un solo encabezado puede saltarse el middleware de Next.js.

Veremos cómo añadir una verificación en la capa de acceso a datos para que tu portal sea realmente seguro y no solo esté protegido superficialmente.

¿Qué necesitas para crear un portal con acceso por roles en Webflow Cloud?

Necesitas una cuenta de Clerk y una Webflow Cloud App creada con Next.js.

Aquí tienes la lista completa antes de tocar una sola línea de código:

  • Una cuenta de Clerk (el plan gratuito Hobby cubre hasta 50,000 usuarios retenidos al mes por aplicación, lo cual es suficiente para la mayoría de los portales).
  • Un sitio de Webflow. Webflow Cloud funciona con el plan Starter gratuito, pero vincular tu aplicación a un dominio personalizado requiere un plan de sitio Premium o superior.
  • La CLI de Webflow: npm install -g @webflow/webflow-cli. Requiere Node.js 22.13.0 o superior.
  • Familiaridad básica con el App Router de Next.js. Necesitas saber qué son los Server Components y los Route Handlers para que la lógica del middleware tenga sentido.

No se requieren conocimientos de autenticación de terceros. Clerk abstrae la verificación JWT, la gestión de sesiones y la configuración de OAuth. Defines los roles en un editor JSON en su panel, y el token de sesión lleva esos roles a tu aplicación en cada solicitud.

Una vez que tengas esto, la configuración se realiza en cinco pasos.

5 pasos para añadir la autenticación de Clerk y el acceso basado en roles a una Webflow Cloud App

Los pasos a continuación cubren la creación de la estructura, la configuración del entorno, la personalización del token de sesión, la protección de rutas y la verificación a nivel de página, en ese orden. Cada paso se basa en el anterior; saltarse uno dejará una brecha en tu modelo de seguridad.

Crearemos un portal con dos roles: admin (acceso total) y client (acceso restringido). La estructura funciona para cualquier número de roles. Solo tienes que ampliar los tipos de TypeScript y los comparadores de rutas.

Así es como se construye.

1. Crea la estructura de la aplicación en la nube e instala Clerk

Si aún no has creado la estructura de una aplicación en la nube de Webflow, hazlo primero:

webflow auth login
webflow cloud init

La CLI te pedirá un nombre para el proyecto y creará una aplicación Next.js con wrangler.json, next.config.ts, open-next.config.ts, y webflow.json ya configurados.

Una vez creada la estructura, instala Clerk:

npm install @clerk/nextjs

@clerk/nextjs incluye tanto los componentes de React del lado del cliente como el SDK del lado del servidor. El SDK del servidor se ejecuta en aislamientos V8, por lo que funciona en Cloudflare Workers sin necesidad de polyfills de tiempo de ejecución ni indicadores de compatibilidad. Lo he probado con el adaptador OpenNext y no he tenido problemas.

Lo que obtienes tras este paso: Una aplicación en la nube de Webflow con Clerk instalado y listo para configurar el entorno.

2. Añade tus variables de entorno

Tu aplicación necesita dos claves de Clerk y cuatro URL de redirección. Añádelas a .env.local para el desarrollo local y, a continuación, replica los mismos valores en tu entorno de Webflow Cloud antes de realizar el despliegue.

Crea .env.local en la raíz de tu proyecto:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your_key_here
CLERK_SECRET_KEY=sk_live_your_key_here
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/dashboard

Encontrarás ambas claves en tu panel de control de Clerk, en API Keys. La clave pública (con el prefijo NEXT_PUBLIC_) es segura para exponerla en el navegador. La clave secreta debe permanecer siempre en el lado del servidor. Nunca la incluyas en el control de versiones.

Añade las mismas claves a tu entorno de Webflow Cloud antes de desplegar. En la configuración de tu sitio Webflow, ve a Webflow Cloud, abre tu entorno y añade ambas en Environment Variables.

Luego, envuelve tu app/layout.tsx raíz con <ClerkProvider>:

// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}

<ClerkProvider> hace que el estado de autenticación esté disponible para cada componente de servidor, componente de cliente y gestor de rutas en el árbol. Sin esto, auth() devuelve null y nada funcionará.

Lo que obtienes tras este paso: Clerk integrado en la aplicación. Ejecuta npm run dev. No deberías ver errores en la consola relacionados con Clerk.

3. Configura el token de sesión para incluir roles

Clerk almacena los roles de usuario en publicMetadata. Por defecto, esos metadatos no se incluyen en el token de sesión, por lo que tu aplicación necesitaría una llamada API adicional en cada solicitud para verificar el rol. Para evitarlo, incrusta los metadatos directamente en el token de sesión.

En el panel de control de Clerk, ve a Sessions en la barra lateral izquierda.

En Customize session token, abre el editor de Claims e introduce:

{
  "metadata": "{{user.public_metadata}}"
}

Guárdalo. A partir de ahora, cada token de sesión que emita Clerk incluirá los metadatos públicos del usuario como una reclamación metadata. El asistente auth() en Next.js lee esta reclamación directamente desde la sesión, sin realizar ninguna solicitud de red adicional.

A continuación, crea un tipo de TypeScript para tus roles, así el compilador podrá detectar errores de escritura.

Definición de los tipos de rol de TypeScript

En la raíz de tu proyecto, crea types/globals.d.ts:

// types/globals.d.ts
export {}

export type Roles = 'admin' | 'client'

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      role?: Roles
    }
  }
}

Añade los roles que necesite tu portal. Mantengo este tipo junto a las definiciones de ruta para que se mantengan sincronizados cuando los roles cambien.

Creación del asistente checkRole()

Crea un checkRole() asistente reutilizable en utils/roles.ts:

// utils/roles.ts
import { type Roles } from '@/types/globals'
import { auth } from '@clerk/nextjs/server'

export const checkRole = async (role: Roles) => {
  const { sessionClaims } = await auth()
  return sessionClaims?.metadata.role === role
}

checkRole('admin') devuelve true si la reclamación de sesión del usuario que ha iniciado sesión coincide. De lo contrario, devuelve falso. Lo uso en cada componente de servidor y acción de servidor que maneja datos confidenciales, no solo en el middleware.

Para asignar un rol a un usuario manualmente: ve al Panel de control → Usuarios, selecciona al usuario, desplázate hasta Metadatos del usuario, haz clic en Editar junto a Público y añade:

{
  "role": "admin"
}
Clerk dashboard Users tab showing a user's Metadata panel with empty Public, Private, and Unsafe fields, and the Edit button beside Public highlighted

Más adelante en la guía, añadiré una acción de servidor que permita a los administradores asignar roles a otros usuarios sin tener que acceder al panel de control.

Lo que tienes tras este paso: Metadatos de rol integrados en cada token de sesión. checkRole() está disponible en cualquier parte de tu aplicación.

4. Protege las rutas con clerkMiddleware()

La protección de rutas reside en middleware.ts en la raíz de tu proyecto. La referencia completa de clerkMiddleware() documenta todas las opciones, incluida la protección por tipo de token y la compatibilidad con múltiples dominios.

Next.js 16 deja de usar middleware.ts en favor de proxy.ts, pero no hagas ese cambio en Webflow Cloud. proxy.ts se ejecuta en el entorno de ejecución de Node.js y no puede optar por Edge, y Webflow Cloud solo admite middleware en el entorno de ejecución de Edge. Mantén el archivo llamado middleware.ts en todas las versiones de Next.js:

// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)'])
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
const isClientRoute = createRouteMatcher(['/dashboard(.*)'])

export default clerkMiddleware(async (auth, req) => {
  // Allow sign-in and sign-up pages through without auth
  if (isPublicRoute(req)) return

  // Protect all dashboard routes — redirect to sign-in if unauthenticated
  if (isClientRoute(req)) {
    await auth.protect()
  }

  // Admin routes require the admin role specifically
  if (isAdminRoute(req)) {
    const { sessionClaims, redirectToSignIn } = await auth()

    if (sessionClaims?.metadata.role !== 'admin') {
      const homeUrl = new URL('/', req.url)
      return NextResponse.redirect(homeUrl)
    }
  }
})

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
  ],
}

createRouteMatcher() acepta patrones de ruta con el mismo formato que los patrones de coincidencia de Next.js. El (.*) al final coincide con todas las subrutas: /admin, /admin/users, /admin/settings/billing.

auth.protect() redirige a los usuarios no autenticados a la URL de inicio de sesión que configuraste en NEXT_PUBLIC_CLERK_SIGN_IN_URL. Para las rutas de administración, verifico el rol desde sessionClaims directamente y redirijo a la página de inicio en lugar de al inicio de sesión, porque un usuario cliente que ya ha iniciado sesión y accede a /admin debería aterrizar en un lugar útil, no en una pantalla de inicio de sesión.

Una cosa: no pongas tu lógica de obtención de datos en un componente de servidor y luego la limites solo con middleware. El middleware se ejecuta antes de que se renderice la página, pero el CVE-2025-29927 demostró que, bajo ciertas condiciones, los encabezados pueden omitir el middleware de Next.js en versiones anteriores. Verifica la autenticación de nuevo en la capa de datos en el siguiente paso.

Lo que tienes tras este paso: Usuarios no autenticados redirigidos desde el panel de control y las rutas de administración. Los usuarios autenticados que no son administradores tienen bloqueado el acceso a /admin. Intenta acceder a /dashboard en modo incógnito. Deberías aterrizar en /sign-in.

5. Crea las páginas del panel de control restringidas por rol

El middleware gestiona la redirección. La comprobación a nivel de página gestiona la seguridad. Siempre hago ambas cosas.

Empieza con el panel de control del cliente en app/dashboard/page.tsx:

// app/dashboard/page.tsx
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
import { UserButton } from '@clerk/nextjs'

export default async function DashboardPage() {
  // Second check — do not rely on middleware alone
  const { userId, sessionClaims } = await auth()
  if (!userId) redirect('/sign-in')

  const role = sessionClaims?.metadata.role

  return (
    <main>
      <header>
        <h1>Client Portal</h1>
        <UserButton afterSignOutUrl="/" />
      </header>

      <section>
        <h2>Your Projects</h2>
        <p>Role: {role ?? 'No role assigned'}</p>
        {/* Render role-specific content here */}
      </section>

      {role === 'admin' && (
        <section>
          <h2>Admin Controls</h2>
          <a href="/admin">Open Admin Panel</a>
        </section>
      )}
    </main>
  )
}

La llamada auth() en la página vuelve a leer las reclamaciones de sesión directamente. A diferencia de una redirección de middleware, que se activa antes de que se renderice la página, esta comprobación se ejecuta dentro del propio componente de servidor, por lo que sigue funcionando incluso si una solicitud logra saltarse la capa de middleware.

Creación del panel de administración

Ahora, el panel de administración en app/admin/page.tsx:

// app/admin/page.tsx
import { redirect } from 'next/navigation'
import { checkRole } from '@/utils/roles'
import { clerkClient } from '@clerk/nextjs/server'

export default async function AdminPage() {
  // Double-check even though middleware already redirected non-admins
  const isAdmin = await checkRole('admin')
  if (!isAdmin) redirect('/')

  const client = await clerkClient()
  const { data: users } = await client.users.getUserList({ limit: 50 })

  return (
    <main>
      <h1>Admin Panel</h1>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {users.map((user) => (
            <tr key={user.id}>
              <td>{user.firstName} {user.lastName}</td>
              <td>
                {user.emailAddresses.find(e => e.id === user.primaryEmailAddressId)?.emailAddress}
              </td>
              <td>{(user.publicMetadata.role as string) ?? 'none'}</td>
              <td>
                <form action={setUserRole}>
                  <input type="hidden" name="userId" value={user.id} />
                  <input type="hidden" name="role" value="admin" />
                  <button type="submit">Make Admin</button>
                </form>
                <form action={setUserRole}>
                  <input type="hidden" name="userId" value={user.id} />
                  <input type="hidden" name="role" value="client" />
                  <button type="submit">Make Client</button>
                </form>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </main>
  )
}

La getUserList llamada devuelve 10 usuarios por defecto, por eso el ejemplo funciona { limit: 50 }. Para portales con una base de usuarios mayor, añade offset paginación a la clerkClient().users.getUserList() llamada y renderiza las páginas de resultados.

Añadir la acción de servidor para la gestión de roles

La acción de servidor que actualiza los roles se coloca en app/admin/_actions.ts:

// app/admin/_actions.ts
'use server'

import { checkRole } from '@/utils/roles'
import { clerkClient } from '@clerk/nextjs/server'

export async function setUserRole(formData: FormData) {
  // Verify the acting user is admin — never trust the client
  if (!(await checkRole('admin'))) {
    return { error: 'Not authorized' }
  }

  const client = await clerkClient()
  const userId = formData.get('userId') as string
  const role = formData.get('role') as string

  try {
    await client.users.updateUserMetadata(userId, {
      publicMetadata: { role },
    })
    return { success: true }
  } catch (err) {
    return { error: String(err) }
  }
}

La checkRole('admin') llamada dentro de la acción de servidor es la parte crítica. Incluso si alguien crea un POST directo a /admin y evita el redireccionamiento del middleware, la acción verifica la sesión en el servidor antes de tocar cualquier dato. Este es el patrón de capa de acceso a datos que cierra la brecha CVE-2025-29927.

Añadir páginas de inicio y registro de sesión

Por último, añade las páginas de inicio y registro de sesión usando los componentes de interfaz alojados de Clerk.

Crea :app/sign-in/[[...sign-in]]/page.tsx

// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'

export default function SignInPage() {
  return (
    <main style={{ display: 'flex', justifyContent: 'center', padding: '4rem 0' }}>
      <SignIn />
    </main>
  )
}

Haz lo mismo para app/sign-up/[[...sign-up]]/page.tsx con <SignUp />. El [[...sign-up]] nombre de la carpeta utiliza el enrutamiento "catch-all" opcional de Next.js, que es lo que necesitan los componentes alojados de Clerk para gestionar sus pasos de navegación internos (por ejemplo, verificación de correo electrónico y callbacks de SSO).

Despliega cuando estés listo:

webflow auth login
webflow cloud deploy

Lo que obtienes tras este paso: Un portal totalmente protegido por roles. Los usuarios no autenticados llegan a /sign-in. Los clientes autenticados aterrizan en /dashboard. Los administradores obtienen /dashboard además de /admin con gestión de usuarios. Cada operación con datos sensibles verifica el rol en el lado del servidor.

Qué hace tropezar a los desarrolladores al añadir autenticación a Webflow Cloud

La mayoría de los problemas de autenticación en esta configuración caen en una de estas cuatro categorías.

Aquí tienes la causa de cada uno y cómo solucionarlo.

auth()devuelve nulo en un componente de servidor

El <ClerkProvider> en app/layout.tsx falta o está dentro de un componente de cliente ('use client'). <ClerkProvider> debe ser una raíz del lado del servidor. Muévelo al layout más externo y asegúrate de que el archivo de layout no tenga ninguna 'use client' directiva en la parte superior.

Los roles no aparecen ensessionClaims.

El paso de Clerk Dashboard → Sessions → Customize session token se omitió o se guardó incorrectamente. Abre el editor de Claims, confirma que contiene { "metadata": "{{user.public_metadata}}" }, y guarda de nuevo.

La reclamación (claim) se integra al crear la sesión. Los usuarios que ya habían iniciado sesión antes de que añadieras la reclamación no la tendrán hasta que cierren y vuelvan a iniciar sesión.

middleware.tsno se está ejecutando en producción

La matriz de config.matcher middleware controla qué rutas pasan por clerkMiddleware(). Si /dashboard no está en el patrón de coincidencia, el middleware nunca se activa para esas rutas y la protección desaparece silenciosamente. Verifica que el buscador incluya las rutas de tu portal. La expresión regular del ejemplo anterior cubre todas las rutas no estáticas de forma predeterminada.

El panel de administración es accesible para los usuarios cliente a pesar del middleware

Esto casi siempre significa que el patrón isAdminRoute no coincide con la ruta real. Un patrón de /admin solo coincide exactamente con /admin , no con /admin/users. Usa /admin(.*) para hacer coincidir todas las subrutas.

Añade un console.log(req.nextUrl.pathname) en el middleware temporalmente para confirmar qué ruta está activando la comprobación.

Conecta tu portal a una capa de datos

Ahora tienes un portal de cliente que registra a los usuarios, les asigna roles y les muestra solo lo que deben ver. El panel de administración te permite gestionar roles sin acceder directamente al panel de control de Clerk. Una vez que un portal tenga más de un puñado de usuarios, esto te hará falta.

La siguiente capa natural son los datos. Combino esta configuración de autenticación con un enlace de base de datos en Webflow Cloud (ya sea el almacenamiento SQLite integrado o un servicio externo como Supabase), para que el panel muestre registros reales específicos del cliente en lugar de contenido de marcador de posición.

Explora Webflow + Firebase para obtener un enfoque full-stack alternativo que combina datos en tiempo real y autenticación en un solo servicio.

Explora Webflow + Authy para añadir contraseñas de un solo uso basadas en tiempo a tu flujo de inicio de sesión de Clerk para portales que requieran autenticación de dos factores.

Preguntas frecuentes

¿Funciona Clerk en el entorno de ejecución Cloudflare Workers de Webflow Cloud?

Sí. El SDK de @clerk/nextjs Clerk se ejecuta en aislamientos V8, que es el entorno de ejecución que utiliza Cloudflare Workers. El adaptador OpenNext de Webflow Cloud gestiona el puente entre Next.js y el entorno Workers, y la verificación de sesiones de Clerk es compatible con él. He desplegado esta pila en producción en Webflow Cloud sin necesidad de indicadores de compatibilidad ni polyfills.

¿Es suficiente el middleware para proteger rutas sensibles?

No, y el CVE-2025-29927 lo demostró. El middleware en el borde es tu primera línea de defensa y gestiona la redirección de la experiencia de usuario, pero puede ser evitado bajo ciertas condiciones en versiones anteriores de Next.js. El patrón correcto es usar middleware para redirecciones más la verificación de checkRole() o auth() en cada punto de acceso a datos: componentes de servidor, acciones de servidor y controladores de ruta. Juntas, ambas capas te ofrecen una defensa en profundidad.

¿Tengo que gestionar los tokens JWT manualmente?

No. Clerk gestiona la emisión, rotación y verificación de tokens automáticamente. El asistente auth() lee el token de sesión de las cabeceras de la solicitud y devuelve las declaraciones decodificadas. Nunca tocas el JWT sin procesar. La única configuración que haces es decirle a Clerk qué incluir en el token a través del editor de declaraciones en el panel de control.


Última actualización
August 8, 2026
Categoría

Artículos relacionados

Cómo añadir búsqueda con Algolia y optimización multimedia con Cloudinary a una aplicación de Webflow Cloud
Cómo añadir búsqueda con Algolia y optimización multimedia con Cloudinary a una aplicación de Webflow Cloud

Cómo añadir búsqueda con Algolia y optimización multimedia con Cloudinary a una aplicación de Webflow Cloud

Cómo añadir búsqueda con Algolia y optimización multimedia con Cloudinary a una aplicación de Webflow Cloud

Guías
Por
Ismail Ajagbe
,
,
Leer artículo
Cómo añadir una ventana emergente de Calendly a Webflow y mantener a tus visitantes en el sitio
Cómo añadir una ventana emergente de Calendly a Webflow y mantener a tus visitantes en el sitio

Cómo añadir una ventana emergente de Calendly a Webflow y mantener a tus visitantes en el sitio

Cómo añadir una ventana emergente de Calendly a Webflow y mantener a tus visitantes en el sitio

Desarrollo
Por
Colin Lateano
,
,
Leer artículo
Cómo crear un formulario de contacto con envío de correos mediante SendGrid en Webflow Cloud
Cómo crear un formulario de contacto con envío de correos mediante SendGrid en Webflow Cloud

Cómo crear un formulario de contacto con envío de correos mediante SendGrid en Webflow Cloud

Cómo crear un formulario de contacto con envío de correos mediante SendGrid en Webflow Cloud

Guías
Por
Ismail Ajagbe
,
,
Leer artículo
Cómo añadir Gemini AI a un sitio de Webflow de forma segura usando Webflow Cloud
Cómo añadir Gemini AI a un sitio de Webflow de forma segura usando Webflow Cloud

Cómo añadir Gemini AI a un sitio de Webflow de forma segura usando Webflow Cloud

Cómo añadir Gemini AI a un sitio de Webflow de forma segura usando Webflow Cloud

Desarrollo
Por
Colin Lateano
,
,
Leer artículo

verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo

Empieza gratis

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.

Empieza, es gratis
Ver demostración

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.