Tips & Tricks (Atualizado: 02/06/2026)

Gerenciamento seguro de cookies com Claude Code: sessões Next.js, CSRF e consentimento

Implemente cookies seguros no Next.js com Claude Code: HttpOnly, Secure, SameSite, CSRF, logout e consentimento.

Gerenciamento seguro de cookies com Claude Code: sessões Next.js, CSRF e consentimento

Gerenciar cookies parece simples até virar a causa de uma invasão de conta. Um cookie de sessão é apenas um valor curto em um cabeçalho HTTP, mas o navegador o envia automaticamente. Isso é útil para autenticação e perigoso quando os atributos estão errados.

Claude Code consegue gerar o código rapidamente, mas um pedido vago como “crie um cookie de login” costuma deixar lacunas. HttpOnly pode faltar, SameSite=None pode aparecer sem Secure, o logout pode não apagar o cookie por causa de Path, e cookies de analytics podem ser misturados com cookies de autenticação.

Este guia usa Next.js App Router e cobre o fluxo completo: inventário, Route Handler copiável, logout, leitura no servidor, CSRF, prevenção de session fixation, comportamento do navegador, limite de consentimento, comandos de verificação, referências oficiais e CTA de monetização.

Comece pelo inventário de cookies

Antes de definir atributos, nomeie o propósito. Um cookie de autenticação é uma credencial. Um cookie de preferência guarda tema ou idioma. Um cookie de analytics ou publicidade faz medição e rastreamento. Cada categoria tem um risco e uma regra de consentimento diferentes.

PropósitoExemploAtributos recomendadosLimite de consentimento
Sessão de autenticação__Host-sessionHttpOnly, Secure, SameSite=Lax, Path=/, Max-Age curtoGeralmente necessário para o serviço solicitado, mas confirme regras locais
Token CSRFcsrf-tokenSecure, SameSite=Lax, Max-Age curtoCookie de apoio de segurança, não ID de analytics
Preferência de UItheme, localeSecure, SameSite=Lax, vida limitadaExplique conforme região e política
Analytics ou anúncios_ga, campaign IDSomente após consentimento quando exigidoSeparado de login e checkout

HttpOnly significa que o JavaScript do navegador não consegue ler o cookie por document.cookie. Secure restringe o envio a HTTPS, com exceções práticas para localhost. SameSite define quando o cookie viaja em requisições cross-site. Max-Age é uma duração em segundos; Expires é uma data absoluta.

A MDN recomenda em Secure cookie configuration reduzir o escopo com Secure, HttpOnly, SameSite e prefixos. A referência Set-Cookie também explica que SameSite=None exige Secure, e que Max-Age tem prioridade sobre Expires quando ambos existem.

Para sessão, prefira o prefixo __Host-. Em navegadores compatíveis, ele exige Secure, proíbe Domain e exige Path=/. Isso reduz a chance de um subdomínio sobrescrever o identificador de sessão.

A documentação atual do Next.js para cookies descreve cookies() como API assíncrona e lista opções como httpOnly, secure, sameSite, maxAge, path e domain. Server Components podem ler cookies; setar e apagar deve acontecer em Route Handlers ou Server Actions.

Crie app/api/login/route.ts. O exemplo usa um Map em memória para teste local. Em produção, troque por Redis, PostgreSQL, DynamoDB ou outro session store 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;
}

O token é regenerado a cada login bem-sucedido. Isso combate session fixation, ataque em que a aplicação não troca o ID de sessão durante a autenticação. A OWASP descreve o problema em Session Fixation.

Logout e leitura no servidor

Apagar cookie depende do mesmo escopo. Se você criou com Path=/, apague com Path=/. Se usou Domain, repita o mesmo Domain. Com __Host-, não use 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;
}

Em produção, também revogue o registro de sessão no servidor. Apagar só no navegador não invalida um token roubado que ainda existe no store.

Leitura do lado 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>;
}

Não trate a presença do cookie como autenticação. O servidor deve verificar store, expiração, revogação, usuário e permissões.

CSRF não é resolvido por HttpOnly

CSRF é cross-site request forgery: outro site faz o navegador autenticado enviar uma ação indesejada. Como o navegador envia cookies automaticamente, HttpOnly protege contra leitura por JavaScript, mas não impede o envio.

A OWASP recomenda em CSRF Prevention Cheat Sheet usar tokens para requisições que mudam estado. Este helper gera token assinado ligado à sessão.

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));
}

Exija token para POST, PUT, PATCH e DELETE. Não mude estado com GET. SameSite=Lax ajuda, mas não substitui token, verificação de Origin e desenho correto de métodos HTTP.

Comportamento do navegador e expiração

O navegador não expõe Set-Cookie ao JavaScript do frontend. Você vê em DevTools ou com curl -i, mas não lê em headers de fetch(). Em requisições cross-origin, CORS e credentials precisam estar corretos.

Max-Age é uma duração relativa em segundos. Expires é uma data específica. Em código de aplicação, Max-Age costuma ser mais previsível e menos dependente do relógio do cliente. Se ambos existem, Max-Age prevalece.

SameSite=Lax permite certas navegações de topo com métodos seguros. Um endpoint GET que altera estado continua sendo falha. Strict é mais forte, mas pode atrapalhar links externos. None só deve ser usado quando há contexto cross-site real, sempre com Secure.

Limite de consentimento e casos de uso

O limite de consentimento separa cookies necessários para o serviço de cookies de analytics, publicidade e experimentos. A política de cookies da Comissão Europeia mostra essa separação entre consentimento, autenticação e analytics. Isto não é aconselhamento jurídico, mas a regra de engenharia é clara: segurança de autenticação não deve depender de consentimento de marketing.

Caso 1: login SaaS. Use __Host-session, Max-Age curto, revogação no servidor, token CSRF e novo token após login. Admin e cobrança podem exigir SameSite=Strict e verificação extra.

Caso 2: site de conteúdo com PDF gratuito, produtos e consulta. Medir CTA é útil, mas rejeitar analytics não pode quebrar download, compra, login ou formulário.

Caso 3: idioma e tema. Esses cookies podem precisar ser lidos por JavaScript, então talvez não sejam HttpOnly. Nunca coloque token, papel, preço ou permissão neles.

Falhas comuns

Primeira falha: __Host-session sem Secure, com Domain ou sem Path=/. O prefixo perde o sentido.

Segunda falha: logout que não apaga porque Path ou Domain não bate.

Terceira falha: acreditar que SameSite resolve CSRF sozinho. Token, método HTTP e validação no servidor continuam necessários.

Quarta falha: tokens em localStorage ou cookies legíveis pelo cliente. Com XSS, eles vazam.

Quinta falha: banner de consentimento bloqueando cookies de segurança. Rejeitar analytics não deve desligar login, carrinho, checkout ou CSRF.

Prompt e verificação

Prompt recomendado:

Implemente um cookie de login para Next.js App Router.

Requisitos:
- nome: __Host-session
- atributos: HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age
- não definir Domain
- gerar novo session token a cada login correto
- logout com Max-Age=0 e o mesmo Path
- não misturar analytics consent com autenticação
- explicar CSRF token para requisições que mudam estado
- revisar com MDN, Next.js e OWASP

Verifique o 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"}'

Formato esperado:

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

Verifique o logout:

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

A resposta deve ter o mesmo nome, Path=/ e Max-Age=0. Com Playwright, use context.cookies() para verificar httpOnly, secure, sameSite e expiração.

Para o fluxo completo de autenticação, veja o guia de autenticação com Claude Code, a comparação de autenticação JWT e o guia de auditoria de segurança. Referências oficiais: MDN Set-Cookie, Next.js cookies, OWASP Session Management e OWASP CSRF Prevention.

Para prompts reutilizáveis, checklists e templates de review, veja os produtos ClaudeCodeLab. Se sua equipe precisa aplicar isso em um repositório real com consentimento, checkout e revisão de segurança, siga para treinamento e consultoria.

Ao testar este fluxo, o maior ganho foi explicitar o contrato: __Host-, escopo do logout, token CSRF e limite de analytics. Um pedido curto como “deixe os cookies seguros” ainda deixava detalhes importantes para correção manual.

#Claude Code #Cookie #session #security #Next.js #TypeScript
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.