Cookie を深く理解する — HTTP のステートレスを超える仕組み¶
はじめに¶
Cookie は Web 開発の基盤中の基盤だ。ログイン維持、カート管理、言語設定の保持——日常的に使う Web 機能のほとんどが Cookie に依存している。にもかかわらず「なんとなく分かっている」状態のエンジニアは多い。
この記事では、HTTP のステートレス性から出発して、Cookie がなぜ必要で、どう動き、どんな属性で守られているのかを体系的に整理する。EC サイトのカート、ログインのセッション管理、広告のサイト横断追跡という 3 つのユースケースを軸に、実務で必要な深さまで踏み込む。
なお、この記事の内容は Slidev で作成した発表資料(inbox/books/Presentation/cookie.md)をベースに、記事向けに再構成・加筆したものである。
背景・前提知識¶
HTTP はステートレス¶
HTTP の基本は「リクエストとレスポンスの 1 往復で完結する」こと。サーバーは 1 回の通信が終わると、そのブラウザのことを覚えていない。
これがステートレスの意味だ。レストランのレジで、会計のたびに店員が前の客を完全に忘れる状態に近い。
ステートレスの問題¶
ユーザーが求めるのはステートフルな体験——「さっきの続き」ができること——だ。
| やりたいこと | ステートレスだと |
|---|---|
| ログイン後にマイページを見る | 「この人は誰?」になる |
| カートに商品を追加する | 前の商品が消える |
| 言語設定を日本語にする | 次のページで英語に戻る |
この問題を解決するのが Cookie だ。
本論¶
Cookie とは何か¶
サーバーがブラウザに保存を指示する小さなテキストデータ。ブラウザはそれを保持し、同じサーバーへのリクエスト時に自動で送り返す。
重要なのは、ブラウザが勝手に作るのではなく、サーバーが Set-Cookie ヘッダーで発行するということ。Cookie はサーバーが選んだ情報をクライアント側に預ける仕組みだ。
Cookie の送信ルール¶
Cookie はどこにでも送られるわけではない。
| 状況 | 送信される? |
|---|---|
https://example.com で発行 → https://example.com にアクセス |
送られる |
https://example.com で発行 → https://other.com にアクセス |
送られない |
| シークレットモードで保存 | ウィンドウを閉じると消える |
Amazon の Cookie が Google に送られないのは、この制限があるからだ。
Cookie の有効期限¶
| 種類 | 挙動 | 使い所 |
|---|---|---|
| セッション Cookie | ブラウザを閉じると消える | 一時的なログイン、CSRF トークン |
| 永続 Cookie | Max-Age や Expires で指定した期限まで残る |
言語設定、テーマ、ログイン維持 |
「ログインは残るのにシークレットモードでは消える」のは、この保存期間の違いで説明できる。
ユースケース 1: EC サイトのカート¶
ログインしていなくても、Cookie でそのブラウザのカートを識別できる。
流れ:
- 初回訪問: サーバーがカート識別子を発行 →
Set-Cookie: cart_id=xyz - 商品を追加: ブラウザが
Cookie: cart_id=xyzを自動送信 → サーバーはxyzのカートに商品を追加 - 後日アクセス: 同じ Cookie から前回のカートを復元
ポイントは、Cookie に入っているのが「カートの中身そのもの」ではなく、カートを引くための ID であること。実際の商品一覧や数量はサーバー側で管理する。
EC サイトで使われる Cookie 属性:
| 属性 | 例 | 役割 |
|---|---|---|
| Cookie 名と値 | cart_id=xyz |
カートを引く識別子 |
Max-Age |
2592000(30日) |
ブラウザを閉じてもカートを残す |
Secure |
— | HTTPS 通信だけに限定 |
HttpOnly |
— | JavaScript から読めなくする |
DevTools で確認する方法¶
- EC サイト(gihyo.jp など)で未ログインのまま商品をカートに入れる
F12→ Network タブでSet-CookieとCookieヘッダーを確認する- Application → Cookies で Cookie の名前・値・期限を確認する
- ブラウザを閉じて再訪問し、カートが残っているか確かめる
ユースケース 2: ログインとセッション管理¶
ログインの主役は認証とセッション管理。Cookie はその結果として発行された Session ID を運ぶ役 だ。
Cookie とセッションの関係¶
| Cookie | セッション | |
|---|---|---|
| どこにある? | ブラウザ側 | サーバー側 |
| 役割 | 手がかりを運ぶ | 本体データを持つ |
| 典型例 | session_id=abc |
abc → ログイン中, カート2件 |
ホテルで言うと、Cookie はカードキー、セッションはフロント側の宿泊台帳だ。
ログインの流れ¶
ブラウザ → サーバー: POST /login (認証情報)
サーバー: 認証成功 → セッション作成 → session_id=abc を発番
サーバー → ブラウザ: 302 /mypage + Set-Cookie: session_id=abc
ブラウザ: session_id=abc を保存
ブラウザ → サーバー: GET /mypage + Cookie: session_id=abc
サーバー: abc を照合 → ログイン済みと判断
サーバー → ブラウザ: 200 OK (マイページ)
POST /login の直後に 302 リダイレクトするのは、再読込時に「フォームを再送信しますか?」が出るのを防ぐため。Set-Cookie はリダイレクト応答でも保存される。
セッションの構造¶
セッションには有効期限があり、一定時間操作がないとタイムアウトする。期限切れの Session ID が送られてもログアウト状態として扱う。
最小実装例(Node.js)¶
const sessions = new Map()
app.post('/login', (req, res) => {
// 1. Session ID を作る
const sessionId = crypto.randomUUID()
// 2. サーバー側に状態を保存
sessions.set(sessionId, { userId: 42 })
// 3. Cookie に載せて返す
res.cookie('session_id', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
})
// 4. 画面は GET に移す
res.redirect('/mypage')
})
Go での実装例¶
package main
import (
"crypto/rand"
"encoding/hex"
"net/http"
"sync"
"time"
)
type Session struct {
UserID int
ExpiresAt time.Time
}
var (
sessions = make(map[string]*Session)
mu sync.RWMutex
)
func generateSessionID() string {
b := make([]byte, 32)
rand.Read(b)
return hex.EncodeToString(b)
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// 認証処理(省略)
sessionID := generateSessionID()
mu.Lock()
sessions[sessionID] = &Session{
UserID: 42,
ExpiresAt: time.Now().Add(24 * time.Hour),
}
mu.Unlock()
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: sessionID,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
Path: "/",
})
http.Redirect(w, r, "/mypage", http.StatusFound)
}
func mypageHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_id")
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
mu.RLock()
session, ok := sessions[cookie.Value]
mu.RUnlock()
if !ok || time.Now().After(session.ExpiresAt) {
http.Error(w, "Session Expired", http.StatusUnauthorized)
return
}
// session.UserID でユーザー情報を取得して表示
}
ログアウトの流れ¶
ログアウトにはサーバー側のセッション破棄とブラウザ側の Cookie 削除の両方が必要。
Max-Age=0 を受け取った時点で、ブラウザは Cookie を削除する。
ログイン用 Cookie の属性¶
| 属性 | 例 | 役割 |
|---|---|---|
| Cookie 名 | __Host-session_id=abc |
__Host- プレフィックスで安全性を強制 |
HttpOnly |
— | JavaScript から Session ID を隠す(XSS 対策) |
Secure |
— | HTTPS のときだけ送信 |
SameSite |
Lax or Strict |
CSRF 攻撃を抑制 |
ユースケース 3: 広告とサイト横断追跡¶
「さっき見ていた商品が別サイトの広告に出る」——これも Cookie の仕組みだ。
ファーストパーティ Cookie とサードパーティ Cookie¶
| ファーストパーティ | サードパーティ | |
|---|---|---|
| 発行元 | 訪問中のサイト自身 | 別ドメイン(広告ネットワーク等) |
| 用途 | ログイン維持、カート | サイト横断の追跡・広告配信 |
サイト横断追跡の仕組み¶
サイト A にアクセスした場合:
- サイト A のページに広告ネットワーク(ad-network)の広告枠がある
- ブラウザが ad-network に広告を読み込みに行く
- ad-network が
Set-Cookie: user=xxxを返す(サードパーティ Cookie)
別のサイト B にアクセスした場合:
- サイト B にも同じ ad-network の広告枠がある
- ブラウザが
Cookie: user=xxxを自動で送る - ad-network は「この人はサイト A もサイト B も見た」と判明
- サイト A で見た商品に関連する広告を返す
サードパーティ Cookie の規制状況(2026 年現在)¶
| ブラウザ | 対応 |
|---|---|
| Safari | 2020 年〜 全面ブロック(ITP) |
| Firefox | 2023 年〜 デフォルトブロック(ETP / Total Cookie Protection) |
| Chrome | ユーザー選択制へ移行(強制的な廃止は見送り) |
CHIPS(Cookies Having Independent Partitioned State)¶
2025 年末から主要ブラウザで利用可能になった新しい仕組み。サードパーティ Cookie を埋め込み先のサイトごとに分離する。
Partitioned 属性を付けると、同じ ad-network の Cookie でも、サイト A で保存されたものはサイト A からのアクセス時のみ送信される。サイト B では別の Cookie が使われるため、サイト横断の追跡ができなくなる。
要件:
- SameSite=None が必須
- Secure が必須
- Chrome 114 以降で対応(Firefox は独自の Total Cookie Protection で同等機能を提供、Safari はサードパーティ Cookie 自体をブロック)
Cookie の安全属性まとめ¶
| 属性 | 効果 | いつ使う |
|---|---|---|
Secure |
HTTPS 通信時のみ送信 | 常に付ける |
HttpOnly |
JavaScript からアクセス不可 | Session ID など機密性の高い Cookie |
SameSite=Lax |
別サイトからの POST では送らない | デフォルト(明示推奨) |
SameSite=Strict |
別サイトからは一切送らない | 高セキュリティが必要な場面 |
SameSite=None |
クロスサイトでも送る(Secure 必須) |
サードパーティ用途 |
__Host- プレフィックス |
Secure + Path=/ + ドメイン指定なしを強制 |
ログイン Cookie |
Partitioned |
埋め込み先サイトごとに分離 | CHIPS 対応のサードパーティ Cookie |
Cookie 単体でできること vs Session ID と組み合わせること¶
| Cookie 単体 | Session ID と組み合わせ |
|---|---|
theme=dark — テーマ設定 |
Session ID でサーバー側のログイン状態を引く |
lang=ja — 言語設定 |
Session ID でカートの中身を引く |
consent=yes — 同意状態 |
Session ID でユーザー権限を引く |
cart_id=xyz — カート識別子 |
共通するのは、Cookie 自体に全情報を持たせるのではなく、サーバー側の状態を引くための手がかりを運ぶという発想だ。
実践・ユースケース¶
バックエンドエンジニアが押さえるべきチェックリスト¶
- ログイン Cookie には
HttpOnly+Secure+SameSite=Laxを必ず付ける - Session ID は暗号論的に安全な乱数で生成する(Go なら
crypto/rand) - ログアウト時はサーバー側セッション破棄 +
Max-Age=0で Cookie 削除の両方を行う - セッションにタイムアウトを設定する(無期限のセッションは危険)
__Host-プレフィックスの使用を検討する(Cookie の安全要件を強制できる)
DevTools で Cookie を確認する手順¶
F12→ Network タブでリロード- 任意のリクエストをクリック → Headers →
Set-Cookie/Cookieを確認 - Application → Storage → Cookies で名前・値・属性・期限を一覧確認
- ログイン前後で Cookie の増減を比較する
まとめ¶
- Cookie はサーバーが
Set-Cookieでブラウザに保存させる小さなテキストデータ - ブラウザは次回以降、発行元サイトへの対応リクエストでその Cookie を自動送信する
- Cookie 単体で設定値を持てるし、Session ID と組み合わせればサーバー側の状態も引ける
- EC のカート、ログイン維持、広告追跡はすべて同じ発想の延長線上にある
- セキュリティ属性(
Secure,HttpOnly,SameSite)で Cookie を適切に守る - サードパーティ Cookie は規制が進み、CHIPS(
Partitioned) が新たな選択肢になっている
関連する発表資料¶
このリポジトリには Slidev で作成した Cookie の発表スライドがある。
inbox/books/Presentation/cookie.md— Cookie・セッション・広告の仕組みを図解したスライドinbox/books/Presentation/Web技術入門.md— URL・HTTP・Cookie を日常の例でつなげるスライド
ローカルで閲覧する場合: