Manajemen Cookie Aman dengan Claude Code: Session Next.js, CSRF, dan Consent
Implementasikan cookie Next.js yang aman dengan Claude Code: HttpOnly, Secure, SameSite, CSRF, logout, dan consent.
Manajemen cookie terlihat kecil sampai menjadi penyebab akun diambil alih. Cookie session hanyalah nilai pendek di header HTTP, tetapi browser mengirimkannya otomatis. Itu membuatnya praktis untuk autentikasi, sekaligus berbahaya kalau atributnya salah.
Claude Code bisa membuat kode dengan cepat, tetapi prompt seperti “buat cookie login” sering belum cukup. HttpOnly bisa lupa dipasang. SameSite=None bisa muncul tanpa Secure. Logout bisa gagal karena Path berbeda. Cookie analytics juga bisa tercampur dengan cookie autentikasi, padahal keduanya punya batas consent yang berbeda.
Artikel ini memakai Next.js App Router sebagai contoh: inventaris cookie, Route Handler yang bisa dicoba, logout, pembacaan sisi server, CSRF, pencegahan session fixation, perilaku browser, batas consent, perintah verifikasi, referensi resmi, dan CTA monetisasi.
Mulai dari inventaris cookie
Sebelum memilih atribut, tulis tujuan setiap cookie. Cookie autentikasi adalah kredensial. Cookie preferensi menyimpan bahasa atau tema. Cookie analytics dan iklan dipakai untuk pengukuran atau pelacakan. Ketiganya tidak boleh disatukan dalam aturan consent dan keamanan yang sama.
| Tujuan | Contoh | Atribut yang disarankan | Batas consent |
|---|---|---|---|
| Session autentikasi | __Host-session | HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age pendek | Biasanya perlu untuk layanan yang diminta, tetapi cek aturan lokal |
| Token CSRF | csrf-token | Secure, SameSite=Lax, Max-Age pendek | Cookie pendukung keamanan, bukan ID analytics |
| Preferensi UI | theme, locale | Secure, SameSite=Lax, masa hidup terbatas | Jelaskan sesuai wilayah dan kebijakan |
| Analytics atau iklan | _ga, campaign ID | Set hanya setelah consent bila diwajibkan | Pisahkan dari login dan checkout |
HttpOnly berarti JavaScript di browser tidak bisa membaca cookie lewat document.cookie. Secure membatasi pengiriman ke HTTPS, dengan perlakuan khusus untuk localhost. SameSite mengatur kapan cookie ikut pada request cross-site. Max-Age adalah durasi dalam detik; Expires adalah tanggal absolut.
MDN dalam Secure cookie configuration menyarankan pembatasan scope dengan Secure, HttpOnly, SameSite, dan prefix. Referensi Set-Cookie juga menjelaskan bahwa SameSite=None membutuhkan Secure, dan Max-Age lebih diprioritaskan daripada Expires jika keduanya ada.
Untuk session, gunakan prefix __Host- bila memungkinkan. Browser yang mendukung hanya menerima cookie __Host- jika memakai Secure, tanpa Domain, dan Path=/. Ini mengurangi risiko subdomain menimpa cookie session.
Set cookie session aman di Next.js
Dokumentasi Next.js untuk cookies menjelaskan cookies() sebagai API async dan mendukung opsi seperti httpOnly, secure, sameSite, maxAge, path, dan domain. Server Components bisa membaca cookie; set dan delete dilakukan di Route Handler atau Server Action.
Buat app/api/login/route.ts. Contoh ini memakai Map di memori agar bisa diuji cepat. Untuk production, ganti dengan Redis, PostgreSQL, DynamoDB, atau session store lain yang persisten.
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;
}
Token dibuat ulang setiap login berhasil. Ini membantu mencegah session fixation, yaitu serangan ketika aplikasi tidak mengganti session ID saat autentikasi. OWASP menjelaskan pola ini di Session Fixation.
Logout dan baca di sisi server
Menghapus cookie bukan hanya mencocokkan nama. Browser juga mencocokkan scope. Jika cookie dibuat dengan Path=/, hapus dengan Path=/. Jika memakai Domain, penghapusan harus memakai Domain yang sama. Dengan __Host-, jangan set 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;
}
Di production, hapus atau revoke session di server juga. Menghapus cookie browser saja tidak selalu membuat token yang sudah dicuri langsung tidak valid.
Baca cookie di server dengan await cookies():
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>;
}
Jangan anggap “cookie ada” berarti user sudah sah. Server harus memeriksa session store, expiry, status revoke, user ID, dan permission.
CSRF tidak selesai dengan HttpOnly
CSRF adalah cross-site request forgery: situs lain membuat browser yang sudah login mengirim request yang tidak diinginkan. Karena browser mengirim cookie otomatis, HttpOnly melindungi dari pembacaan JavaScript, tetapi tidak menghentikan pengiriman cookie.
OWASP di CSRF Prevention Cheat Sheet merekomendasikan token untuk request yang mengubah state. Helper berikut membuat token bertanda tangan yang terikat ke session.
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));
}
Minta token untuk POST, PUT, PATCH, dan DELETE. Jangan mengubah state lewat GET. SameSite=Lax membantu sebagai defense in depth, tetapi bukan pengganti token, pemeriksaan Origin, dan desain HTTP method yang benar.
Perilaku browser dan masa berlaku
Browser tidak mengekspos Set-Cookie ke JavaScript frontend. Header itu terlihat di DevTools atau curl -i, tetapi tidak terbaca dari response headers fetch(). Untuk request cross-origin, CORS dan credentials juga harus benar.
Max-Age adalah durasi relatif dalam detik. Expires adalah tanggal tertentu. Dalam kode aplikasi, Max-Age biasanya lebih mudah dan tidak terlalu bergantung pada selisih jam client dan server. Jika keduanya ada, Max-Age yang diprioritaskan.
SameSite=Lax masih mengizinkan beberapa navigasi top-level dengan metode aman. Jadi endpoint GET yang mengubah data tetap salah. Strict lebih kuat, tetapi bisa mengganggu alur dari email atau link luar. None hanya untuk kebutuhan cross-site nyata dan harus selalu memakai Secure.
Batas consent dan use case
Batas consent memisahkan cookie yang diperlukan untuk layanan dari cookie analytics, iklan, dan eksperimen. Cookies policy Komisi Eropa juga memisahkan preferensi consent, autentikasi, dan analytics sebagai tujuan berbeda. Ini bukan nasihat hukum, tetapi aturan engineering-nya jelas: keamanan autentikasi jangan digabung dengan consent marketing.
Use case pertama adalah login SaaS. Gunakan __Host-session, Max-Age pendek, revoke di server, token CSRF, dan token baru setelah login. Untuk admin atau billing, pertimbangkan SameSite=Strict dan verifikasi tambahan.
Use case kedua adalah situs konten dengan PDF gratis, produk, dan form konsultasi. Mengukur CTA berguna, tetapi menolak analytics tidak boleh merusak download, pembelian, login, atau form.
Use case ketiga adalah bahasa dan tema. Cookie ini kadang perlu dibaca JavaScript, jadi tidak selalu HttpOnly. Jangan pernah menyimpan token, role, harga, atau entitlement di sana.
Kegagalan yang sering terjadi
Pertama, __Host-session tanpa Secure, memakai Domain, atau lupa Path=/. Nilai keamanan prefix hilang.
Kedua, logout tidak menghapus cookie karena Path atau Domain berbeda dari saat set.
Ketiga, menganggap SameSite sudah cukup untuk CSRF. Token, HTTP method, Origin, dan validasi server tetap perlu.
Keempat, menyimpan token di localStorage atau cookie yang bisa dibaca client. Jika XSS terjadi, token bocor.
Kelima, banner consent memblokir cookie keamanan. Menolak analytics tidak boleh mematikan login, cart, checkout, atau perlindungan CSRF.
Prompt dan verifikasi
Prompt yang lebih aman:
Implementasikan cookie login untuk Next.js App Router.
Syarat:
- nama cookie: __Host-session
- atribut: HttpOnly, Secure, SameSite=Lax, Path=/, Max-Age
- jangan set Domain
- buat session token baru setiap login berhasil
- logout dengan Max-Age=0 dan Path yang sama
- jangan campur cookie analytics consent dengan cookie autentikasi
- jelaskan CSRF token untuk request yang mengubah state
- review memakai dokumentasi MDN, Next.js, dan OWASP
Cek header 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"}'
Bentuk yang diharapkan:
Set-Cookie: __Host-session=...; Path=/; Max-Age=28800; HttpOnly; Secure; SameSite=Lax
Cek logout:
curl -i -X POST http://localhost:3000/api/logout
Response harus berisi nama yang sama, Path=/, dan Max-Age=0. Dengan Playwright, gunakan context.cookies() untuk memeriksa httpOnly, secure, sameSite, dan expiry.
Link, CTA, dan hasil uji
Untuk alur autentikasi lengkap, lanjutkan ke panduan autentikasi Claude Code, artikel JWT authentication, dan panduan security audit. Referensi resmi: MDN Set-Cookie, Next.js cookies, OWASP Session Management, dan OWASP CSRF Prevention.
Untuk prompt, checklist, dan template review yang bisa dipakai ulang, lihat produk ClaudeCodeLab. Jika tim perlu menerapkan aturan ini di repository nyata dengan consent, checkout, dan security review, lanjutkan ke training dan konsultasi.
Saat mencoba workflow ini, peningkatan terbesar datang dari kontrak yang eksplisit: __Host-, scope logout, token CSRF, dan batas analytics consent. Prompt pendek seperti “buat cookie aman” masih sering menyisakan detail yang harus diperbaiki manual.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.