Tres lugares donde se puede detener una petición
Una petición a una página del admin en una app de Next.js pasa por hasta tres lugares que pueden rechazarla: el proxy, que corre antes que cualquier página; el código del servidor que renderiza la página o ejecuta la acción; y, si los datos viven detrás de una API aparte, esa API.
No son tres copias de la misma revisión. El proxy solo ve la petición: la ruta, las cabeceras, las cookies. La página y la capa de datos pueden preguntarle a la librería de autenticación si la sesión es real. La API puede verificar su propia prueba de quién llama. Cada una atrapa algo que la anterior no puede.
El error común es quedarse en la primera. En una demo funciona: cierras sesión, abres /admin y te manda a iniciar sesión. Lo que la demo no muestra es una petición con una cookie que el proxy no puede juzgar, o una que ni siquiera pasa por el proxy.
Por qué el proxy no puede ser la única revisión
La guía de autenticación de Next.js (en inglés) llama optimista a la revisión del proxy: debe leer la sesión de la cookie sin ir a la base de datos, porque el proxy corre en cada ruta, incluidas las que se precargan. La misma guía dice que el proxy no debería ser tu única línea de defensa y que la mayoría de las revisiones de seguridad van lo más cerca posible de los datos.
Las librerías de autenticación dicen lo mismo de sus ayudas para el proxy. La documentación de Better Auth para Next.js (en inglés) dice que getSessionCookie solo revisa que exista una cookie de sesión, sin validarla, y que depender solo de eso es peligroso, porque cualquiera puede crear una cookie a mano.
- puntaje CVSS del CVE-2025-29927, calificado como crítico
- 9.1
- el día que Next.js publicó el aviso
- 21 mar 2025
- líneas de versiones corregidas: 12.3.5, 13.5.9, 14.2.25 y 15.2.3
- 4
Y está el caso en que el proxy ni siquiera corre. El CVE-2025-29927 (en inglés) permitía que una petición se saltara las revisiones de autorización hechas en el middleware, y la solución temporal del aviso era bloquear las peticiones externas con la cabecera x-middleware-subrequest. Una app cuya única revisión vivía ahí se quedaba sin ninguna.
// proxy.ts: un filtro rápido, no la cerradura
import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
export function proxy(request: NextRequest) {
// Solo revisa que exista la cookie. No la valida.
if (!getSessionCookie(request)) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/admin/:path*"] };La revisión real va donde se leen los datos
La respuesta de la guía es una capa de acceso a datos: una función que solo corre en el servidor, verifica la sesión y devuelve el usuario, y a la que llama cada función que lee o cambia datos del admin. Si la revisión vive ahí, ninguna página, acción o ruta llega a los datos sin pasar por ella, y nadie tiene que acordarse de agregarla.
En un admin, verificar quiere decir tres cosas: que la sesión sea real (la librería la consulta, no solo mira la cookie), que el usuario tenga el rol correcto y que siga en la lista de personas con acceso. Lo último importa cuando alguien sale del equipo: su sesión puede seguir siendo válida, pero la lista dice que no.
// lib/dal.ts: la revisión que llama cada lectura y escritura del admin
import "server-only";
import { cache } from "react";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
export const requireStaff = cache(async () => {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) redirect("/sign-in"); // sin sesión, o cookie falsa
if (!isAllowed(session.user.email)) redirect("/sign-in"); // tiene sesión, pero no es del equipo
return session.user;
});Los layouts no cuentan
Una revisión en un layout no basta. Next.js conserva los layouts entre navegaciones sin volver a renderizarlos, así que la sesión no se revisa en cada cambio de ruta, y un layout no impide que corran las páginas que tiene debajo.
Las acciones y las rutas revisan por su cuenta
Las Server Actions y los Route Handlers se pueden llamar directamente, sin la página que muestra el botón. La guía aconseja tratarlos como endpoints públicos de una API:
- Cada Server Action que cambia datos del admin llama primero a la misma función de la capa de datos, aunque la página que la muestra ya lo haya hecho.
- Cada Route Handler del admin también revisa, y responde 401 si no hay sesión y 403 si el usuario no tiene permiso.
- Esconder un botón a quien no puede usarlo es buena interfaz, pero no es seguridad: la acción que hay detrás igual revisa.
Si los datos están en una API aparte
Muchos admins no leen una base de datos directamente: el servidor de Next.js llama a una API del backend. Esa API no debería confiar en la app web solo porque la petición viene de ella. Una cabecera que diga "esto es del equipo" la puede mandar cualquiera que llegue a la API.
Un patrón más seguro: después de su propia revisión, el servidor web emite un token de vida corta firmado con una clave privada, y la API verifica la firma, el vencimiento y quién lo emitió contra las claves públicas que publica la app web. Un token robado deja de servir en minutos, y la API nunca comparte un secreto con la app web.
Mantén la vida del token corta y su contenido mínimo: quién es el usuario, quién lo emitió, para quién es y cuándo vence. La API debería rechazar un token cuyo emisor o destinatario no coincida, y poder traer claves públicas nuevas cuando la app web las rote, para que un cambio de clave no deje a todos afuera.

Una lista para revisar tu propio admin
Nada de esto pide una librería nueva. Pide las revisiones en el lugar correcto, y una forma de comprobar que están. La tabla resume las capas; la lista que sigue son pruebas que puedes hacer en una tarde.
| Capa | Revisa | Detiene | No puede detener |
|---|---|---|---|
| Proxy | que exista una cookie de sesión | visitas casuales, rápido | una cookie falsa; una petición que se lo salta |
| Capa de datos | sesión, rol, lista de acceso | cookies falsas, usuarios quitados | una API que confía en cualquiera |
| Server Actions y rutas | la misma función, otra vez | llamadas directas sin la página | nada, si la llaman |
| API del backend | su propio token firmado y de vida corta | peticiones que evitan la app web | una clave de firma filtrada |
Un proxy puede rechazar gente rápido. No debería ser lo único que hay delante de los datos.
- Busca en tu código cada lugar donde se leen o cambian datos del admin, y confirma que todos llamen a la misma función de verificación.
- Inicia sesión, sácate de la lista de acceso y recarga: deberías quedar fuera.
- Manda una petición con una cookie de sesión inventada: el proxy la deja pasar, y la capa de datos debería detenerla.
- Llama a una Server Action o a una ruta de la API directamente, sin la página: debería responder 401 o 403.
- Compara tu versión de Next.js con los avisos de seguridad y mantenla al día.
Si estás construyendo una herramienta interna o un portal para clientes y quieres las reglas de acceso pensadas desde el principio, nuestro servicio de plataformas web lo cubre, o envíanos tu proyecto.


