Next.js
Cookies y headers
Explica cómo leer y escribir cookies y headers en Next.js, proteger sesiones, evitar CSRF y spoofing, y mantener compatibilidad con streaming y caché.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Explica cómo leer y escribir cookies y headers en Next.js, proteger sesiones, evitar CSRF y spoofing, y mantener compatibilidad con streaming y caché.
Cookies y headers forman parte del contexto de una request. Son útiles para sesión, preferencias, negociación y políticas HTTP, pero todo valor recibido del cliente es no confiable. Leerlos puede hacer dinámica una región; escribirlos exige una respuesta cuyos headers todavía no hayan sido enviados.
browser request
├─ Cookie header
├─ Authorization
├─ Accept-Language
├─ User-Agent
└─ custom headers
↓
Next.js server context
↓
response headers
├─ Set-Cookie
├─ Cache-Control
├─ Content-Security-Policy
└─ LocationCookies son headers especializados administrados por el navegador. No son almacenamiento privado del servidor.
La API es asíncrona:
import { cookies } from "next/headers";
export default async function AccountPage() {
const cookieStore = await cookies();
const sessionToken = cookieStore.get("session")?.value;
const user = await getUserFromSession(sessionToken);
return <Account user={user} />;
}Leer cookies() depende del request actual y vuelve dinámica esa región. Con Cache Components debe permanecer fuera de use cache normal y bajo una boundary apropiada.
Se escribe en contextos que controlan response headers:
"use server";
import { cookies } from "next/headers";
export async function setLocale(locale: string) {
const parsed = localeSchema.parse(locale);
const store = await cookies();
store.set("locale", parsed, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 365,
});
}También pueden devolver Set-Cookie antes del streaming.
Un Server Component general puede leer, pero no debe mutar cookies durante render.
HTTP envía headers antes del body:
status + headers
↓
HTML stream begins
↓
later React segmentsDespués del primer byte no puedes añadir Set-Cookie de forma confiable. Por eso login/logout y preferencias se realizan en Actions, Handlers o Proxy.
Impide que JavaScript cliente lea la cookie. Reduce robo directo por XSS, pero una página comprometida todavía puede enviar requests autenticadas mientras el navegador adjunta la cookie.
Solo la envía por HTTPS. Debe estar activo en producción.
Strict: protección fuerte, puede romper flujos externos.Lax: opción común para sesiones web; permite ciertas navegaciones top-level.None: cross-site, exige Secure y aumenta necesidad de defensa CSRF.Limita rutas donde se envía. Usa el alcance mínimo compatible.
Una cookie de dominio amplio puede llegar a subdominios menos confiables. Prefiere host-only cuando sea posible.
Max-Age/Expires controlan persistencia en navegador. La sesión servidor puede expirar antes; verifica ambas.
login
→ verify credentials/OAuth callback
→ create server-side session row
→ random session ID in HttpOnly cookie
→ requests resolve session IDVentajas:
Costes:
claims + expiry
→ sign/encrypt
→ cookieVentajas:
Costes:
No pongas perfiles completos ni secretos. La firma evita modificación, no oculta datos; el cifrado sí oculta, pero añade gestión criptográfica.
Usa librerías mantenidas y algoritmos estándar.
Cookies tienen límites por navegador, normalmente alrededor de pocos KB por cookie y número limitado por dominio. Payload grande viaja en cada request y puede provocar 431 Request Header Fields Too Large.
Almacena un identificador, no una copia de toda la aplicación.
Una cookie se adjunta automáticamente. Un sitio atacante puede intentar enviar una operación al tuyo.
Defensas:
SameSite apropiado.Origin/Host en operaciones.CORS no bloquea necesariamente el envío; controla lectura desde JavaScript.
Tras login o elevación de privilegios, crea un ID nuevo. No reutilices un ID suministrado por el atacante.
Rota sesiones:
Invalida otras sesiones cuando el producto lo requiera.
"use server";
export async function logout() {
const store = await cookies();
const token = store.get("session")?.value;
if (token) await revokeSession(token);
store.delete("session");
redirect("/login");
}Borrar solo el navegador deja la sesión servidor válida si el token fue robado. Revoca ambos.
import { headers } from "next/headers";
export default async function Page() {
const requestHeaders = await headers();
const userAgent = requestHeaders.get("user-agent");
}Es read-only y asíncrona. También implica request-time.
No utilices user-agent para construir dos árboles incompatibles salvo necesidad; feature detection cliente suele ser más robusta.
El navegador controla:
User-Agent.La infraestructura puede añadir:
Solo confía en headers forwarded cuando la plataforma elimina valores del cliente y agrega los suyos. Documenta la cadena de proxies.
const host = normalizeHost(requestHeaders.get("host"));
const tenant = await resolveAllowedTenantHost(host);No interpolar el host directamente en una query o redirect. Valida:
Un host correcto no autoriza al usuario.
Para mutaciones:
const origin = request.headers.get("origin");
if (!allowedOrigins.has(origin ?? "")) reject();Origin puede faltar en algunos requests legítimos. Diseña una política que considere método, autenticación y cliente. Referer contiene más información y puede omitirse por políticas de privacidad; no es la única defensa.
Para API tokens:
const value = request.headers.get("authorization");
const token = parseBearer(value);
const principal = await verifyAccessToken(token);No registres el header. Revisa expiración, audience, issuer, scopes y revocation según el mecanismo.
Route Handler:
return Response.json(data, {
headers: {
"Cache-Control": "private, no-store",
"X-Request-Id": requestId,
},
});Proxy/config pueden aplicar políticas. Evita enviar headers internos como IDs de base de datos o roles.
Si una respuesta cambia por Accept-Language:
Vary: Accept-LanguageSin Vary, una CDN puede servir español a un usuario inglés. Pero variar por Cookie completa puede crear una key por usuario y destruir caché.
Para personalización privada usa private/no-store o separa shell cacheada de región dinámica.
Orden:
URL /es
→ cookie locale
→ Accept-Language
→ defaultLa URL explícita debe dominar para enlaces compartibles. La cookie recuerda preferencia; el header solo sugiere.
Tamaño, exposición y datos obsoletos.
Roles pueden cambiar; revalida operaciones sensibles.
El navegador la rechaza y expone riesgo.
No existe response mutation segura durante render.
Spoofing.
Elimina caché compartida.
Token robado sigue activo.
cookies() y headers() son asíncronas y dinámicas.Vary y cookies afectan caché.Vary es necesario?Set-Cookie?Authentication en Next.js diseña cómo credenciales se transforman en una sesión verificable y revocable.