Claude CodeでCookie管理を安全に実装する:Next.js認証・CSRF・同意境界
Claude CodeでNext.jsの安全なCookie管理を実装。HttpOnly、SameSite、CSRF、同意境界を実例で解説。
Cookie管理は「ログイン状態を保存するだけ」の作業に見えます。しかし実際には、XSS、CSRF、セッション固定、同意管理、サブドメイン運用まで一気に関わる小さなセキュリティ設計です。
Claude Codeに「Cookieを設定して」とだけ頼むと、動くコードは出ます。ただし HttpOnly が抜ける、SameSite=None なのに Secure がない、ログアウトで Path が一致せず消えない、同意が必要な分析Cookieと認証Cookieを混ぜる、といった事故が起きやすいです。
この記事では、Next.js App Routerを前提に、コピペして試せるCookie実装、CSRF対策、セッション固定の防止、同意境界、検証コマンドまでまとめます。専門用語も初出で言い換えます。たとえば HttpOnly は「JavaScriptから読ませない旗」、SameSite は「別サイトからのリクエストにCookieを付けるかの規則」です。
先に決めるCookie設計
最初にCookieを3種類に分けます。認証Cookieは本人確認の鍵です。設定Cookieはテーマや言語など、漏れても被害が限定的な好みです。分析・広告Cookieは読者行動の計測に使うため、多くの地域で同意管理の対象になります。
| 用途 | 例 | 推奨設定 | 同意境界 |
|---|---|---|---|
| 認証・セッション | __Host-session | HttpOnly, Secure, SameSite=Lax, Path=/, 短めの Max-Age | サービス提供に必要なCookieとして扱う。ただし法務確認は必要 |
| CSRFトークン | csrf-token | Secure, SameSite=Lax, 短めの Max-Age | 認証保護の補助。広告や分析に流用しない |
| UI設定 | theme, locale | Secure, SameSite=Lax, 必要最小限の期限 | 地域や用途により説明が必要 |
| 分析・広告 | _ga など | 同意後に発行、拒否時は発行しない | 認証Cookieとは別レイヤーで管理 |
MDNのSecure cookie configurationは、Secure、HttpOnly、SameSite、__Host- prefixを組み合わせてCookieの範囲を絞ることを推奨しています。OWASPのSession Management Cheat Sheetも、セッションIDには Secure、HttpOnly、明示的な SameSite を使うべきだと説明しています。
__Host- prefixは特に重要です。名前が __Host- で始まるCookieは、対応ブラウザでは Secure が必要で、Domain を指定できず、Path=/ が必要です。つまり、サブドメインから同じ名前のCookieを上書きしにくくなります。認証Cookieは session より __Host-session のように名付ける方が安全です。
Next.jsで認証Cookieを発行する
Next.jsのcookies APIは、2026年時点のドキュメントで非同期APIとして説明されています。Server ComponentではCookieを読めますが、Cookieの設定や削除はRoute HandlerまたはServer Actionで行います。HTTPレスポンスのヘッダーでブラウザへ指示するため、ストリーミング開始後には設定できません。
次の例は、app/api/login/route.ts に置ける最小のログインRoute Handlerです。デモ用にメモリ上のMapへセッションを保存しています。本番ではRedis、PostgreSQL、DynamoDBなど、サーバー再起動や複数インスタンスに耐えるストアへ置き換えてください。
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;
}
このコードのポイントは4つです。HttpOnly でブラウザJavaScriptからセッションIDを読ませない。Secure でHTTPS送信に限定する。SameSite=Lax で通常のリンク遷移は許しつつ、多くのクロスサイトPOSTを止める。__Host- と Path=/ でサブドメインやパスによる上書き余地を減らす。
SameSite=None はOAuthコールバック、別ドメインの埋め込み、サードパーティ文脈などで必要になることがあります。ただしMDNのSet-Cookieが示す通り、SameSite=None には Secure が必要です。通常の自社アプリ認証では、まず Lax か Strict を選びます。
ログアウトとサーバー側読み取り
ログアウトで多い失敗は「発行時と削除時のスコープが一致しない」ことです。Path=/ で発行したなら、削除も同じ Path=/ にします。Domain を発行時に指定したなら削除時も合わせる必要があります。__Host- Cookieでは Domain を指定しないため、この事故を減らせます。
app/api/logout/route.ts の例です。本番ではCookie削除に加えて、RedisやDB側のセッション行も無効化してください。
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;
}
サーバー側で読む場合は、Server ComponentやRoute Handlerで cookies() を使います。Next.js 15以降を前提に、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>;
}
ここで大事なのは、Cookieの存在だけでログイン済みと判断しないことです。セッションストアで期限、ユーザーID、無効化状態を確認します。期限切れセッションを見つけたら、レスポンス側で Max-Age=0 を返してブラウザからも消します。
CSRFとセッション固定を分けて考える
CSRFは「別サイトが、ログイン済みユーザーのブラウザに勝手なリクエストを送らせる攻撃」です。ブラウザはCookieを自動で送るため、HttpOnly だけでは防げません。OWASPのCSRF Prevention Cheat Sheetは、状態を変えるリクエストにCSRFトークンを入れてサーバーで検証することを推奨しています。
次の例は、セッションIDに紐づいた署名付きCSRFトークンです。フォーム送信や fetch の前にトークンを取得し、X-CSRF-Token ヘッダーで送ります。XSSがあるとCSRF対策は破られやすいので、入力エスケープやCSPも別途必要です。
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));
}
セッション固定は別の問題です。攻撃者が先に作ったセッションIDを被害者に使わせ、ログイン後にそのIDで乗っ取る攻撃です。対策は、ログイン成功時に必ず新しいセッションIDを発行することです。上のNext.js例はログインのたびに createSessionToken() を呼ぶので、この基本を満たしています。Expressで express-session を使う場合は req.session.regenerate() をログイン成功時に呼びます。
Max-AgeとExpires、ブラウザ挙動の落とし穴
Max-Age は「今から何秒で期限切れにするか」です。Expires は「この日時で期限切れにするか」です。両方を指定した場合、MDNは Max-Age が優先されると説明しています。サーバーとブラウザの時計ずれを避けるため、アプリコードでは Max-Age を主に使うのが扱いやすいです。
ブラウザ挙動で覚えておく点は次の通りです。
Set-Cookieはレスポンスヘッダーだが、フロントエンドJavaScriptからは読めない。- CORS越しの
fetchでは、credentialsを正しく指定しないとCookie送受信が期待通りにならない。 HttpOnlyCookieはdocument.cookieから読めないが、同一サイトのfetchにはブラウザが自動で付ける。SameSite=Laxは万能なCSRF対策ではない。GETで状態変更する設計は避ける。Pathは送信範囲の制御であり、同じホスト上の別パスからCookieを読まれない保証ではない。
Claude Codeにレビューさせるなら、「Cookie属性だけでなく、状態変更APIがGETになっていないか、ログアウトでPathが一致しているか、CORSとcredentialsの組み合わせが正しいかも確認して」と指示します。
同意境界と3つのユースケース
同意境界とは「同意なしに置けるCookie」と「同意後にだけ置くCookie」を分ける線です。欧州委員会のCookies policyでも、Cookie同意の保存、認証、分析など用途ごとに説明しています。実務では国や地域で差があるため、この記事は法的助言ではありません。ただし実装方針として、認証・セキュリティCookieを広告計測と混ぜないことは重要です。
ユースケース1は、SaaSのログインです。__Host-session を短めの Max-Age で発行し、サーバー側のセッションストアで失効できます。管理者画面や請求画面では SameSite=Strict も検討します。
ユースケース2は、メディアサイトの言語・テーマ設定です。これは HttpOnly にしない場合があります。ブラウザJavaScriptから読む必要があるためです。ただし認証情報や権限情報は絶対に入れません。
ユースケース3は、広告・分析の計測です。同意前には発行せず、拒否した読者にもログインや購入が壊れないようにします。ClaudeCodeLabのようなコンテンツサイトでは、無料PDF、教材、相談CTAの計測は収益改善に役立ちますが、認証Cookieと目的を分離して実装します。
よくある失敗例
一つ目は、Secure を本番だけにして __Host- Cookieを壊すことです。__Host- には Secure、Path=/、Domain なしが必要です。ローカル確認では localhost またはHTTPS環境でヘッダーを見ます。
二つ目は、SameSite=None を雑に使うことです。クロスサイト送信を許すため、必要な場面だけに絞ります。OAuthや外部決済で必要な場合も、戻り先、stateパラメータ、CSRF検証をセットで設計します。
三つ目は、ログアウトでCookieが消えないことです。原因の多くは Path、Domain、プロトコル不一致です。Next.jsの公式ドキュメントも、削除は同じドメイン・同じプロトコルのRoute HandlerまたはServer Actionで行う必要があると説明しています。
四つ目は、セッションIDをローカルストレージに入れることです。XSS時に一発で盗まれます。認証セッションは HttpOnly Cookieに置き、権限はサーバーで確認します。
五つ目は、Cookieバナーで認証Cookieまで止めることです。ユーザーが分析Cookieを拒否しても、ログイン、カート、購入、CSRF保護は壊してはいけません。
Claude Codeへの依頼と検証コマンド
安全に生成させるには、実装依頼とレビュー依頼を分けます。
Next.js App RouterのRoute Handlerで認証Cookieを実装してください。
条件:
- Cookie名は __Host-session
- HttpOnly, Secure, SameSite=Lax, Path=/, Max-Ageを明示
- Domainは指定しない
- ログイン成功時に毎回新しいセッションIDを発行
- ログアウトでは同じPathでMax-Age=0を返す
- CSRF対策と同意が必要な分析Cookieを混ぜない
- 公式ドキュメントに照らして危険な属性をレビューする
ローカルでヘッダーを確認するコマンドです。
curl -i -X POST http://localhost:3000/api/login \
-H "Content-Type: application/json" \
-d '{"email":"masa@example.com","password":"correct-horse-battery-staple"}'
期待する Set-Cookie は次の形です。
Set-Cookie: __Host-session=...; Path=/; Max-Age=28800; HttpOnly; Secure; SameSite=Lax
ログアウトも確認します。
curl -i -X POST http://localhost:3000/api/logout
Max-Age=0 が返り、発行時と同じ Path=/ になっていれば第一段階は合格です。Playwrightで確認するなら、ログイン後に context.cookies() で httpOnly、secure、sameSite、expires を見ると、ブラウザが実際に保存した属性まで確認できます。
関連リンク、CTA、実際に試した結果
認証全体の設計はClaude Code認証実装ガイドも参考になります。JWTとの使い分けはClaude Code JWT認証、セキュリティ全体のレビューはClaude Codeセキュリティ監査に進むとつながります。
公式資料として、MDN Set-Cookie、MDN Secure cookie configuration、Next.js cookies API、OWASP Session Management、OWASP CSRF Preventionを確認してください。
ClaudeCodeLabの教材では、Cookie単体ではなく、認証、CSRF、CORS、同意、CTA計測を同じチェックリストで扱います。個人で型を固めたい場合は無料チートシートから始め、実装テンプレートが必要ならプロダクト一覧へ進んでください。チームの実リポジトリに合わせて導入する場合は研修・相談ページが自然な導線です。
この記事で紹介した内容を実際に試した結果、Claude Codeに最初から「__Host-、削除時の Path、SameSite=None の禁止条件、CSRFトークン、同意境界」を明記すると、生成コードの修正回数がかなり減りました。逆に「Cookieを安全にして」という短い依頼では、ログアウトや同意管理の抜けが残りやすかったです。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Code権限セーフティラダー: 初心者がallowを広げる順番
Claude Codeの権限をread-onlyからbuild、限定編集、deploy確認まで段階的に広げる安全な運用手順。
Claude Code Small PR Proof Pack: 小さなPRをレビュー可能にする証拠セット
Claude Codeの小さなPRに、差分・検証・公開URL・CTA・rollbackを添える実務チェックリスト。
Claude Codeのコミット前レビューゲート: 差分、テスト、CTAをまとめて止める型
Claude Codeでcommit前に差分をレビューする実践手順。build、公開URL、CTA、Gumroadリンク、未翻訳本文を検知します。