この記事の要点
• Cookieはサーバーがブラウザにデータを保存し、次回以降のリクエストで自動送信される仕組み
• Set-Cookieヘッダーでサーバーが保存し、Cookieヘッダーでブラウザが送信
• SameSite=Laxがデフォルトで、CSRF攻撃を防ぐ重要な防御策
Cookieとは
Cookieは、サーバーがブラウザに保存を依頼する小さなテキストデータです。HTTPはステートレス(状態を持たない)なため、Cookieを使ってセッション管理、ユーザー識別、設定の保存などを実現します。
なぜ必要か: HTTPリクエストは独立しており、「前回ログインしたユーザー」を識別できません。Cookieがあることで、サーバーは「このリクエストは誰からか」を判断できます。
Cookieの基本フロー
1. サーバーが Set-Cookie ヘッダーで送信
ログイン成功時など、サーバーがCookieをブラウザに保存します。
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
Content-Type: text/html
2. ブラウザが Cookie ヘッダーで返送
次回以降、同じドメインへのリクエストには自動的にCookieが付与されます。
GET /dashboard HTTP/1.1
Host: example.com
Cookie: sessionId=abc123
3. サーバーが Cookie を元にユーザー識別
// サーバー側(Node.js例)
app.get('/dashboard', (req, res) => {
const sessionId = req.cookies.sessionId;
const user = sessions[sessionId];
if (user) {
res.send(`Welcome, ${user.name}!`);
} else {
res.redirect('/login');
}
});
ポイント: CookieはブラウザがHTTPヘッダーで自動送信します。JavaScriptで明示的に送る必要はありません(HttpOnly属性があればJSからアクセスもできません)。
Set-Cookie の属性
サーバーがCookieを送信する際、さまざまな属性で動作を制御できます。
| 属性 | 説明 | 例 |
|---|---|---|
| Domain | Cookieが送信されるドメイン | Domain=example.com |
| Path | Cookieが送信されるパス | Path=/api |
| Expires | 有効期限(日時) | Expires=Wed, 21 Oct 2026 07:28:00 GMT |
| Max-Age | 有効期限(秒数) | Max-Age=3600 (1時間) |
| Secure | HTTPS通信でのみ送信 | Secure |
| HttpOnly | JavaScriptからアクセス不可 | HttpOnly |
| SameSite | クロスサイトリクエストでの送信制御 | SameSite=Strict / Lax / None |
完全な例
Set-Cookie: sessionId=abc123; Domain=example.com; Path=/; Max-Age=86400; Secure; HttpOnly; SameSite=Lax
意味:
sessionId=abc123: Cookieの名前と値Domain=example.com: example.comとサブドメインに送信Path=/: 全てのパスに送信Max-Age=86400: 24時間有効Secure: HTTPSのみHttpOnly: JavaScript不可SameSite=Lax: クロスサイトGETは送信、POSTは不送信
SameSite 属性の詳細
CSRF(Cross-Site Request Forgery)攻撃を防ぐ最重要属性です。
| 値 | クロスサイトGET | クロスサイトPOST | 用途 |
|---|---|---|---|
| Strict | 送信しない | 送信しない | 最高セキュリティ(使いにくい) |
| Lax | 送信する | 送信しない | デフォルト・推奨 |
| None | 送信する | 送信する | サードパーティCookie(要Secure) |
Strict
Set-Cookie: sessionId=abc123; SameSite=Strict
動作:
example.com内のリンククリック → Cookie送信other.comからexample.comへのリンク → Cookie送信しない
問題点: 外部サイトからのリンクでログイン状態が解除されたように見える。
Lax(推奨)
Set-Cookie: sessionId=abc123; SameSite=Lax
動作:
example.com内のリンク → Cookie送信other.comからexample.comへのGETリンク → Cookie送信other.comからexample.comへのPOST → Cookie送信しない
利点: ユーザビリティとセキュリティのバランスが良い。CSRF攻撃の大半を防げる。
None(サードパーティCookie)
Set-Cookie: trackingId=xyz789; SameSite=None; Secure
動作: 全てのクロスサイトリクエストで送信。
用途:
- 広告トラッキング
- 埋め込みコンテンツ(YouTube埋め込み等)
- OAuth/SSOプロバイダー
必須条件: Secure属性が必要(HTTPSのみ)。
注意: SameSite=Noneには必ずSecure属性が必要です。HTTPでは動作しません。また、ブラウザのサードパーティCookie廃止方針により、今後使用が制限されます。
Secure 属性と HttpOnly 属性
Secure
Set-Cookie: sessionId=abc123; Secure
効果: HTTPS通信でのみCookieを送信。HTTP通信では送信されない。
用途: セッションID、認証トークンなど機密情報には必須。
HttpOnly
Set-Cookie: sessionId=abc123; HttpOnly
効果: document.cookieでJavaScriptからアクセスできない。
用途: XSS(Cross-Site Scripting)攻撃でのCookie盗難を防ぐ。
例(攻撃の防御):
// HttpOnlyがない場合、XSS攻撃でCookieを盗める
console.log(document.cookie); // sessionId=abc123
// HttpOnlyがある場合
console.log(document.cookie); // sessionIdは表示されない
ポイント: セッションCookieにはHttpOnly + Secure + SameSite=Laxの3点セットが基本です。
Domain と Path の動作
Domain 属性
Set-Cookie: token=abc; Domain=example.com
送信される範囲:
example.com→ ✓www.example.com→ ✓(サブドメインも含む)api.example.com→ ✓other.com→ ✗
省略した場合: 設定元のホストのみ(サブドメインは含まない)。
Set-Cookie: token=abc
# www.example.comで設定した場合
送信される範囲:
www.example.com→ ✓example.com→ ✗api.example.com→ ✗
Path 属性
Set-Cookie: token=abc; Path=/api
送信されるパス:
/api→ ✓/api/users→ ✓/→ ✗/dashboard→ ✗
デフォルト: 設定元のパス。通常はPath=/で全体に送信。
Expires と Max-Age の使い分け
Max-Age(推奨)
Set-Cookie: sessionId=abc; Max-Age=3600
効果: 現在時刻から3600秒(1時間)後に期限切れ。
利点: 相対時間なので、サーバー・ブラウザの時刻ずれの影響を受けない。
Expires
Set-Cookie: sessionId=abc; Expires=Wed, 21 Oct 2026 07:28:00 GMT
効果: 指定日時に期限切れ。
欠点: サーバーとブラウザの時刻がずれていると、意図しない動作になる。
セッションCookie
Set-Cookie: tempData=xyz
有効期限を指定しないと、ブラウザを閉じるまで有効な「セッションCookie」になります。
実践メモ: Max-Age=0でCookieを即座に削除できます。ログアウト処理で使います。
Cookie の容量制限
| 制限項目 | 上限 |
|---|---|
| 1つのCookieサイズ | 約4KB |
| 1ドメインあたりのCookie数 | 約50〜180個(ブラウザ依存) |
| 全Cookie合計サイズ | 約4KB × Cookie数 |
注意点:
- 大きなデータはCookieではなく
localStorageやsessionStorageを使う - Cookieが多いとHTTPヘッダーが肥大化してパフォーマンス低下
実装例
Express.js(Node.js)
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
// ログイン時にCookie設定
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 認証成功したと仮定
const sessionId = generateSessionId();
sessions[sessionId] = { username };
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000 // 24時間(ミリ秒)
});
res.json({ success: true });
});
// Cookieを読み取り
app.get('/profile', (req, res) => {
const sessionId = req.cookies.sessionId;
const user = sessions[sessionId];
if (!user) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({ username: user.username });
});
// ログアウト時にCookie削除
app.post('/logout', (req, res) => {
res.clearCookie('sessionId', {
httpOnly: true,
secure: true,
sameSite: 'lax'
});
res.json({ success: true });
});
生のHTTPレスポンス
HTTP/1.1 200 OK
Set-Cookie: sessionId=a1b2c3d4; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/
Set-Cookie: theme=dark; Max-Age=31536000; Path=/
Set-Cookie: lang=ja; Max-Age=31536000; Path=/
複数のSet-Cookieヘッダーで複数のCookieを設定できます。
クライアント側(JavaScript)
// HttpOnlyではないCookieのみアクセス可能
console.log(document.cookie); // "theme=dark; lang=ja"
// JavaScriptでCookie設定(非推奨、セキュリティリスク)
document.cookie = "preference=compact; max-age=3600";
// 推奨: サーバーにSet-Cookieで設定してもらう
fetch('/api/set-preference', {
method: 'POST',
credentials: 'include', // Cookieを含める
body: JSON.stringify({ theme: 'dark' })
});
注意: JavaScriptでdocument.cookieにセッションIDを保存するのは危険です。XSS攻撃で盗まれます。必ずHttpOnly属性付きでサーバーから設定しましょう。
CSRF攻撃と対策
CSRF攻撃の例
攻撃サイト(evil.com):
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>
SameSite=Noneの場合: ユーザーが攻撃サイトを訪問すると、bank.comへのPOSTにCookieが自動送信され、送金が実行される。
SameSite=Laxの場合: クロスサイトのPOSTリクエストにCookieが送信されず、攻撃失敗。
追加の対策
- CSRFトークン:
// サーバーが発行
res.cookie('csrfToken', generateToken(), { sameSite: 'lax' });
// クライアントがヘッダーで送信
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': getCookie('csrfToken')
}
});
- Referer検証:
app.post('/transfer', (req, res) => {
const referer = req.headers.referer;
if (!referer || !referer.startsWith('https://bank.com')) {
return res.status(403).json({ error: 'Invalid request' });
}
// 処理続行
});
サードパーティCookieの廃止
Chrome、Safari、Firefoxはプライバシー保護のため、サードパーティCookie(SameSite=None)を段階的に廃止しています。
影響を受けるサービス:
- 広告トラッキング
- ソーシャルログイン(一部)
- 埋め込みコンテンツの認証
代替手段:
- CHIPS(Cookies Having Independent Partitioned State): パーティション化Cookie
- FedCM(Federated Credential Management): ブラウザ標準のID連携
- Storage Access API: ユーザー承認で限定的にアクセス
ポイント: ファーストパーティCookie(自社ドメイン内)は影響を受けません。SameSite=Laxで適切に設計すれば、サードパーティCookie廃止後も問題ありません。
デバッグとトラブルシューティング
Chrome DevTools で確認
-
Application タブ → Cookies
- 全Cookieの一覧、属性、値を確認
- 手動で削除・編集可能
-
Network タブ → Response Headers
Set-Cookieヘッダーを確認- 警告があれば表示される(例:
SameSite=None without Secure)
よくある問題
Cookie が送信されない:
- Domain/Path が一致しているか確認
- Secure属性がHTTP通信で使われていないか
- SameSite=Strictでクロスサイトアクセスしていないか
Cookie が保存されない:
- ブラウザの容量制限に達していないか
- Max-AgeやExpiresが過去になっていないか
- サードパーティCookieがブロックされていないか
ベストプラクティス
セッション管理
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/
- HttpOnly: XSS対策
- Secure: 盗聴対策
- SameSite=Lax: CSRF対策
- Max-Age: 適切な期限(24時間など)
ユーザー設定
Set-Cookie: theme=dark; Max-Age=31536000; Path=/
- HttpOnlyは不要(JavaScriptで読み取りたい)
- 長期保存(1年など)
トラッキング(非推奨)
Set-Cookie: trackingId=xyz; SameSite=None; Secure; Max-Age=31536000
プライバシー規制により、今後は使用を避けるべき。
まとめ
Cookieは、HTTPのステートレスな性質を補完し、Webアプリケーションのセッション管理を実現する重要な仕組みです。HttpOnly、Secure、SameSite=Laxの3つの属性を適切に設定することで、XSSやCSRF攻撃からユーザーを守ることができます。サードパーティCookieの廃止を見据え、ファーストパーティCookieを基本とした設計を心がけましょう。
参考リソース
- MDN - HTTP cookies
- MDN - Set-Cookie
- RFC 6265 - HTTP State Management Mechanism
- OWASP - Session Management Cheat Sheet
- Chrome - SameSite Cookie Changes