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.
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ósito | Exemplo | Atributos recomendados | Limite de consentimento |
|---|---|---|---|
| Sessão de autenticação | __Host-session | HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age curto | Geralmente necessário para o serviço solicitado, mas confirme regras locais |
| Token CSRF | csrf-token | Secure, SameSite=Lax, Max-Age curto | Cookie de apoio de segurança, não ID de analytics |
| Preferência de UI | theme, locale | Secure, SameSite=Lax, vida limitada | Explique conforme região e política |
| Analytics ou anúncios | _ga, campaign ID | Somente após consentimento quando exigido | Separado 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.
Defina o cookie de sessão no Next.js
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 === "[email protected]" && 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":"[email protected]","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.
Links, CTA e resultado testado
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.
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.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.