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 === "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.
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
Escada de segurança de permissões no Claude Code
Amplie de read-only para edições limitadas, comandos de prova e deploy checks sem perder controle.
Claude Code Small PR Proof Pack: pequenas mudanças fáceis de revisar
Um pacote de prova para PRs do Claude Code: diff, checks, URL pública, CTA e rollback.
Gate de revisão antes do commit com Claude Code
Revisão antes do commit com Claude Code: diff, build, URL pública, Gumroad, consultoria, testes e arquivos fora do escopo.