Tips & Tricks (Actualizado: 2/6/2026)

Gestión segura de cookies con Claude Code: sesiones Next.js, CSRF y consentimiento

Implementa cookies seguras en Next.js con Claude Code: HttpOnly, Secure, SameSite, CSRF, logout y consentimiento.

Gestión segura de cookies con Claude Code: sesiones Next.js, CSRF y consentimiento

La gestión de cookies parece una tarea pequeña hasta que una cuenta queda expuesta. Una cookie de sesión es solo un valor corto en una cabecera HTTP, pero el navegador la envía automáticamente. Eso la hace cómoda para autenticar y peligrosa cuando los atributos no están bien definidos.

Claude Code puede generar el código muy rápido, pero una instrucción vaga como “pon una cookie de login” suele dejar huecos. Puede faltar HttpOnly, aparecer SameSite=None sin Secure, fallar el logout porque se borra otro Path, o mezclarse la cookie de autenticación con cookies de analítica que dependen del consentimiento.

Esta guía muestra un flujo práctico con Next.js App Router: inventario de cookies, Route Handler copiable, logout, lectura en servidor, CSRF, prevención de session fixation, comportamiento del navegador, límite de consentimiento, comandos de verificación, enlaces oficiales y CTA de monetización.

Empieza con un inventario de cookies

Antes de elegir atributos, define el propósito. Una cookie de autenticación es una credencial. Una cookie de preferencia guarda estado de interfaz, como idioma o tema. Una cookie de analítica o publicidad pertenece a medición y seguimiento. No deberían vivir bajo la misma regla de consentimiento ni el mismo nivel de riesgo.

PropósitoEjemploAtributos recomendadosLímite de consentimiento
Sesión de autenticación__Host-sessionHttpOnly, Secure, SameSite=Lax, Path=/, Max-Age cortoNormalmente necesaria para el servicio solicitado, pero revisa la norma local
Token CSRFcsrf-tokenSecure, SameSite=Lax, Max-Age cortoCookie de apoyo de seguridad, no identificador de analítica
Preferencia UItheme, localeSecure, SameSite=Lax, vida limitadaExplicación según región y política
Analítica o anuncios_ga, campaign IDSolo después del consentimiento cuando sea obligatorioSeparada de login y checkout

HttpOnly significa que JavaScript del navegador no puede leer la cookie con document.cookie. Secure limita el envío a HTTPS, con tratamiento especial para localhost. SameSite decide cuándo se adjunta la cookie en solicitudes entre sitios. Max-Age expresa la duración en segundos; Expires usa una fecha absoluta.

MDN recomienda en Secure cookie configuration limitar el alcance con Secure, HttpOnly, SameSite y prefijos. La referencia Set-Cookie explica además que SameSite=None requiere Secure, y que Max-Age tiene prioridad sobre Expires si ambos aparecen.

Para sesiones, usa el prefijo __Host- cuando puedas. En navegadores compatibles, una cookie __Host- necesita Secure, no puede tener Domain y debe usar Path=/. Esto reduce la posibilidad de que un subdominio sobrescriba el identificador de sesión.

La documentación actual de Next.js para cookies describe cookies() como una API asíncrona y lista opciones como httpOnly, secure, sameSite, maxAge, path y domain. Los Server Components pueden leer cookies; las mutaciones deben hacerse en Route Handlers o Server Actions.

Crea app/api/login/route.ts con este ejemplo. Usa un Map en memoria para que puedas probar la cabecera al instante. En producción, reemplázalo por Redis, Postgres, DynamoDB u otro almacén persistente.

import { createHmac, randomBytes } from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

export const runtime = "nodejs";

const env = z
  .object({
    NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
    SESSION_SECRET: z.string().min(32),
  })
  .parse(process.env);

const SESSION_COOKIE = "__Host-session";
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 8;

type SessionRecord = {
  userId: string;
  expiresAt: number;
};

declare global {
  var demoSessions: Map<string, SessionRecord> | undefined;
}

const sessions = globalThis.demoSessions ?? new Map<string, SessionRecord>();
globalThis.demoSessions = sessions;

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(12),
});

function createSessionToken() {
  const id = randomBytes(32).toString("base64url");
  const signature = createHmac("sha256", env.SESSION_SECRET)
    .update(id)
    .digest("base64url");

  return `${id}.${signature}`;
}

async function authenticate(email: string, password: string) {
  if (email === "masa@example.com" && password === "correct-horse-battery-staple") {
    return { id: "user_123" };
  }

  return null;
}

export async function POST(request: NextRequest) {
  const body = loginSchema.safeParse(await request.json());
  if (!body.success) {
    return NextResponse.json({ error: "Invalid login payload" }, { status: 400 });
  }

  const user = await authenticate(body.data.email, body.data.password);
  if (!user) {
    return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
  }

  const token = createSessionToken();
  sessions.set(token, {
    userId: user.id,
    expiresAt: Date.now() + SESSION_MAX_AGE_SECONDS * 1000,
  });

  const response = NextResponse.json({ ok: true });
  response.cookies.set({
    name: SESSION_COOKIE,
    value: token,
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: SESSION_MAX_AGE_SECONDS,
  });

  return response;
}

El token se genera de nuevo en cada login correcto. Ese detalle evita session fixation, un ataque en el que la aplicación no cambia el identificador de sesión durante la autenticación. OWASP describe el problema en Session Fixation.

Logout y lectura del lado servidor

Borrar una cookie no consiste solo en usar el mismo nombre. El navegador también compara el alcance. Si la cookie se emitió con Path=/, el logout debe usar Path=/. Si se emitió con Domain, el borrado debe usar el mismo Domain. Con __Host-, no uses Domain.

app/api/logout/route.ts:

import { NextResponse } from "next/server";

const SESSION_COOKIE = "__Host-session";

export async function POST() {
  const response = NextResponse.json({ ok: true });

  response.cookies.set({
    name: SESSION_COOKIE,
    value: "",
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 0,
  });

  return response;
}

En una aplicación real, invalida también el registro de sesión en el servidor. Si solo borras la cookie del navegador, un token robado puede seguir siendo válido hasta que expire en el almacén.

Lectura en servidor:

import { cookies } from "next/headers";
import { redirect } from "next/navigation";

const SESSION_COOKIE = "__Host-session";

export default async function AccountPage() {
  const cookieStore = await cookies();
  const sessionToken = cookieStore.get(SESSION_COOKIE)?.value;

  if (!sessionToken) {
    redirect("/login");
  }

  return <main>Account dashboard</main>;
}

No confundas “hay cookie” con “usuario autenticado”. El servidor debe validar existencia, vencimiento, revocación, usuario y permisos.

CSRF no se resuelve con HttpOnly

CSRF significa cross-site request forgery: otro sitio hace que el navegador autenticado envíe una solicitud no deseada a tu aplicación. Como el navegador adjunta cookies automáticamente, HttpOnly protege contra lectura por JavaScript, pero no impide que la cookie viaje.

OWASP recomienda en CSRF Prevention Cheat Sheet usar tokens para solicitudes que cambian estado. Este helper crea un token firmado ligado a la sesión.

import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";

const CSRF_SECRET = process.env.SESSION_SECRET;
if (!CSRF_SECRET || CSRF_SECRET.length < 32) {
  throw new Error("SESSION_SECRET must be at least 32 characters");
}

export function createCsrfToken(sessionToken: string) {
  const nonce = randomBytes(16).toString("base64url");
  const signature = createHmac("sha256", CSRF_SECRET)
    .update(`${sessionToken}.${nonce}`)
    .digest("base64url");

  return `${nonce}.${signature}`;
}

export function verifyCsrfToken(sessionToken: string, token: string) {
  const [nonce, signature] = token.split(".");
  if (!nonce || !signature) return false;

  const expected = createHmac("sha256", CSRF_SECRET)
    .update(`${sessionToken}.${nonce}`)
    .digest("base64url");

  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Envía el token en X-CSRF-Token o en un campo oculto para POST, PUT, PATCH y DELETE. No cambies estado desde GET. SameSite=Lax ayuda, pero no sustituye el token; XSS también puede romper mitigaciones de CSRF, así que revisa escape de salida y CSP.

Comportamiento del navegador y caducidad

El navegador no expone Set-Cookie a JavaScript de frontend. Lo verás en DevTools o con curl -i, no en las cabeceras leídas por fetch(). En solicitudes cross-origin, CORS y credentials deben estar alineados con los atributos de la cookie.

Max-Age dice cuántos segundos vive la cookie desde que se recibe. Expires usa una fecha exacta. Para código de aplicación, Max-Age suele ser más fácil y menos dependiente del reloj del cliente. Si ambos existen, Max-Age manda.

SameSite=Lax permite navegaciones superiores con métodos seguros, por eso un GET que cambia estado sigue siendo un error. Strict es más fuerte, pero puede afectar enlaces externos o correos. None debe reservarse para contextos cross-site reales y siempre con Secure.

Límite de consentimiento y casos de uso

El límite de consentimiento separa cookies necesarias para el servicio de cookies de analítica, publicidad o experimentos. La política de cookies de la Comisión Europea muestra esa separación práctica entre consentimiento, autenticación y analítica. No es asesoría legal, pero sí una regla técnica: no mezcles seguridad de autenticación con medición comercial.

Caso 1: login SaaS. Usa __Host-session, Max-Age corto, revocación de servidor, CSRF token y nuevo token al iniciar sesión. Para administración o facturación, considera SameSite=Strict y verificación adicional.

Caso 2: sitio de contenidos con PDF gratuito, productos y consultas. Puedes medir CTA, pero rechazar analítica no debe romper descarga, compra, login ni formulario.

Caso 3: preferencias de idioma o tema. A veces deben ser legibles por JavaScript, así que no serán HttpOnly. Nunca guardes tokens, roles, permisos, precios o entitlements en ellas.

Fallos frecuentes

El primer fallo es usar __Host-session sin Secure, con Domain o sin Path=/. El prefijo deja de cumplir su función.

El segundo es un logout que no borra nada porque Path o Domain no coinciden.

El tercero es confiar solo en SameSite para CSRF. Revisa token, método HTTP, Origin y endpoints GET.

El cuarto es poner tokens en localStorage o en cookies legibles por cliente. Un XSS los expone.

El quinto es bloquear cookies de seguridad desde el banner de consentimiento. Rechazar analítica no debe desactivar login, carrito, checkout ni CSRF.

Prompt y verificación

Una instrucción útil para Claude Code:

Implementa una cookie de login para Next.js App Router.

Requisitos:
- nombre: __Host-session
- atributos: HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age
- no usar Domain
- generar nuevo token en cada login correcto
- borrar con Max-Age=0 y el mismo Path
- no mezclar consentimiento de analítica con autenticación
- explicar CSRF token para solicitudes que cambian estado
- revisar contra documentación oficial de MDN, Next.js y OWASP

Verifica el login:

curl -i -X POST http://localhost:3000/api/login \
  -H "Content-Type: application/json" \
  -d '{"email":"masa@example.com","password":"correct-horse-battery-staple"}'

Cabecera esperada:

Set-Cookie: __Host-session=...; Path=/; Max-Age=28800; HttpOnly; Secure; SameSite=Lax

Verifica logout:

curl -i -X POST http://localhost:3000/api/logout

Debe devolver el mismo nombre, Path=/ y Max-Age=0. Con Playwright, usa context.cookies() para comprobar httpOnly, secure, sameSite y expiración.

Enlaces, CTA y resultado probado

Para el flujo completo de autenticación, sigue con la guía de autenticación con Claude Code, la comparación de autenticación JWT y la auditoría de seguridad. Referencias oficiales: MDN Set-Cookie, Next.js cookies, OWASP Session Management y OWASP CSRF Prevention.

Si quieres prompts reutilizables, checklists y plantillas de review, revisa los productos de ClaudeCodeLab. Si necesitas aplicar esto a un repositorio real con consentimiento, checkout y revisión de seguridad, la ruta natural es training y consultoría.

Al probar este flujo, la mejora más clara fue escribir el contrato de cookies antes de pedir cambios: __Host-, alcance del logout, CSRF token y límite de analítica. Con una petición vaga como “haz seguras las cookies”, Claude Code todavía dejaba detalles importantes para revisión manual.

#Claude Code #Cookie #session #security #Next.js #TypeScript
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.