Tech Learning Daily

2026-09-05 (Sat) — 第 51 号
AI が毎朝届ける、ソフトウェア技術の基礎解説
Network 🌿 基礎 ⏱ 約 7 分

HTTP はステートレスなのに、なぜブラウザはログイン状態を覚えていられるのか?

EC サイトでログインしたまま何ページ遷移しても、いちいちログアウトさせられることはない。だが HTTP は「1 つ前のリクエストで誰が来たか」を一切覚えていないステートレスなプロトコルだと聞いたことがあるはずだ。矛盾しているように見えるこの 2 つの事実の間を埋めているのは、たった 1 行のレスポンスヘッダーである。

🎯 3 行まとめ
  • HTTP 自体は各リクエストを独立に処理するステートレスなプロトコルで、サーバーは前回のリクエストを覚えていない。ログイン状態を保つのは Set-Cookie / Cookie ヘッダーというアプリケーション層の仕組みだ。
  • サーバーが Set-Cookie で発行した値を、ブラウザは Domain / Path が一致するリクエストにだけ自動で付け直す。Expires / Max-Age を省略すると「セッション Cookie」としてブラウザを閉じると消える。
  • HttpOnly / Secure / SameSite という 3 つの属性は既定で付いておらず、明示しない限り Cookie は JavaScript から読めて平文回線でも送信される。

「ステートレス」とは何を捨てた設計なのか

ブラウザがサーバーにリクエストを送るたびに、サーバーはそのリクエスト単体だけを見て応答を返す。ステートレスとは、直前のリクエストで誰が何を送ってきたかをサーバー自身が記憶していない性質を指す。1 回のリクエストに含まれる情報だけで処理が完結するため、どのサーバーが応答しても結果が変わらず、ロードバランサーで複数台に処理を分散しやすくなる。

裏を返すと、「さっきログインした人物と、今このリクエストを送っている人物が同一である」という事実を、HTTP プロトコル自身は一切保証しない。この情報を毎回のリクエストに載せて運ぶ役目を担うのが Cookie だ。

Set-Cookie / Cookie ヘッダーが運ぶ往復

サーバーはレスポンスにSet-Cookieヘッダーを付け、任意の名前=値のペアをブラウザに渡せる。ブラウザはこれを保存し、以後Set-Cookieで指定された Domain / Path の条件に一致するリクエストへ、Cookieリクエストヘッダーとして自動で付け直す。この「サーバーが渡し、ブラウザが自動で返す」という一方向の橋渡しがなければ、サーバーは毎回のリクエストがどのユーザーのものかを知る手立てを持たない。

┌── Cookie のラウンドトリップ ──────────┐
│ ① レスポンス(サーバー→ブラウザ)    │
│    Set-Cookie: session_id=abc123     │
│                                       │
│ ② ブラウザが保存                     │
│    Domain/Path が一致する間だけ保持  │
│                                       │
│ ③ 以降のリクエスト(毎回自動で付与) │
│    Cookie: session_id=abc123         │
└───────────────────────────────────────┘

Cookie には有効期限を指定できる。Expires / Max-Age の両方を省略すると「セッション Cookie」として扱われ、ブラウザを閉じると破棄される(両方指定した場合は Max-Age が優先される)。「ブラウザを再起動したらログイン状態が消えていた」という体験の裏には、たいていこの指定漏れがある。

🍱 たとえるなら

Cookie は会員クラブの入館証に近い。受付で Set-Cookie(入館証の発行)を受け取った客は、以降ゲートを通るたびに入館証を提示するだけで、毎回身分証明をやり直さずに済む。ただし、その入館証に HttpOnlySecure に当たる偽造防止の細工が無ければ、ロビーに落ちていた入館証を誰かが拾うだけで、同じ客として振る舞えてしまう。

何も指定しないと危険な 3 つの属性

Set-CookieにはHttpOnlySecureSameSiteという 3 つの属性を追加できるが、いずれも既定では付いていない。HttpOnlyを付けなければ、その Cookie は JavaScript の document.cookie から読み取れる状態のままで、XSS でスクリプトを実行されるとログイン Cookie がそのまま盗まれる。Secureを付けなければ、暗号化されていない HTTP 接続でも Cookie が送信され、通信経路上で盗聴される余地が残る。

SameSite属性は、その Cookie を他サイトを起点にしたリクエストにも送るかを制御する。Strictは自サイト発のリクエストにしか付けず、Laxは自サイト発に加えてリンククリックのような安全なナビゲーションにも付ける。SameSiteを省略した場合、現在の主要ブラウザはLaxとして扱う。ただしLaxでも一部のクロスサイトリクエストには送られてしまうため、CSRF 対策としてはこれだけに頼らずトークン検証を併用する必要がある。

Cookie の中身は「印」なのか「データそのもの」なのか

多くの実装では、Cookie にはセッションを識別するためのランダムな文字列(セッション ID)だけが入っていて、ログインユーザーの実データはサーバー側のデータストアに保存されている、と考えられがちだ。しかし実際には、Cookie 自体にデータそのものを詰め込む実装も広く使われている。Rails のデフォルトのセッションストアである ActionDispatch::Session::CookieStore は、secret_key_base で暗号化した値としてセッションハッシュそのものを Cookie に格納し、サーバー側の追加ストアを必要としない。

この方式には明確な上限がある。Cookie の一般的なサイズ制限はおよそ 4096 バイトで、Rails はこれを超えて session に値を詰めようとすると CookieOverflow 例外を送出する。ユーザー ID やフラッシュメッセージ程度なら問題なく収まるが、複雑なオブジェクトをセッションへ丸ごと入れる実装は、この上限に不意に突き当たることになる。

💼 実務でどう出会うか

ブラウザの DevTools(Chrome の Application タブ、Firefox のストレージタブ)で Cookie の一覧を開くと、Domain・Path・Expires・HttpOnly・Secure・SameSite の列がそのまま並び、今日解説した属性を 1 つずつ確認できる。Rails の session[:user_id] = user.id のような一行は、実際にはこの CookieStore の仕組みに乗ってブラウザへ書き戻されており、セッションに大きなオブジェクトを詰めた際は CookieOverflow で初めてこの仕組みに気づくことになる。

⌨️ 手を動かす(5 分)

リクエスト検証用に公開されている httpbin へ curl を送り、Set-Cookie / Cookie の往復を手動で再現する。

# ① サーバーが Cookie を発行する場面(Set-Cookie を確認)
curl -s -i "https://httpbin.org/cookies/set?session_id=abc123" | grep -i "^set-cookie\|^HTTP"

# ② 受け取った Cookie を次のリクエストで送り返す場面
curl -s -i -b "session_id=abc123" "https://httpbin.org/cookies"

①ではレスポンスヘッダーに set-cookie: session_id=abc123; Path=/ が含まれ、302 で /cookies へリダイレクトされる。②では -b で指定した値が Cookie リクエストヘッダーとして送られ、レスポンスの JSON に {"cookies": {"session_id": "abc123"}} としてサーバー側で受信できたことが確認できる。ブラウザが普段自動でやっている往復を、手動で再現した形だ。

🙅 よくある誤解
  • Cookie を削除すればログアウトしたことになる — ブラウザ側の保存をやめただけで、サーバー側でセッションやトークンを無効化していなければ、盗まれた Cookie の値は有効期限内なら別のブラウザからでもそのまま使える。
  • HttpOnly を付ければ Cookie の通信は安全になる — HttpOnly は JavaScript からの読み取りを防ぐだけで、通信経路が暗号化されているかどうかは Secure 属性が担う別の役割だ。
  • Cookie には常にセッション ID だけが入っている — Rails の CookieStore のように暗号化したデータそのものを Cookie に格納する実装もあり、中身が何を意味するかは実装依存だ。
📖 用語ミニ辞典
ステートレス
サーバーが直前のリクエストの情報を覚えておらず、各リクエストを独立に処理する性質。
Set-Cookie ヘッダー
サーバーがレスポンスに付与し、ブラウザに名前=値のペアを保存させる HTTP ヘッダー。
セッション Cookie
Expires / Max-Age を指定しない Cookie の呼び方。ブラウザを閉じると破棄される。
HttpOnly
その Cookie を JavaScript の document.cookie から読み取れなくする属性。
Secure
暗号化された HTTPS 接続でのみその Cookie を送信させる属性。
SameSite
他サイトを起点にしたリクエストにその Cookie を送るかどうかを制御する属性(Strict / Lax / None)。
🔗 もっと深く
最近の記事

平均レイテンシが正常なのに、なぜ「たまに遅い」と言われるのか?

平均は分布の中心しか表さず、テールに隠れた遅いリクエストがファンアウト構成では大多数のユーザー体験になる。Little's Law で待ち行列が非線形に膨らむ理由と、p99 を使ったタイムアウト・SLO 設計を解説。
Perf🌿 基礎⏱ 約 7 分2026-09-03

HTTP/2 でストリームをまとめたのに、なぜ HTTP/3 は TCP を手放したのか?

HTTP/2 はアプリ層の HOL ブロッキングは解決したが、1 本の TCP に多重化する限りパケットロス時は無関係なストリームまで足止めされる。QUIC がストリームごとに独立した信頼性配送を実装し、コネクション ID で接続を IP・ポートから切り離した設計を解説。
Network🌳 応用⏱ 約 8 分2026-09-02

Rails は自動でエスケープしているのに、なぜ XSS はなくならないのか?

XSS は攻撃者の JS を被害者の権限でブラウザに実行させる攻撃で、防御の要は HTML 本文・属性・JS 文字列・URL という文脈ごとに異なる出力エスケープと、それが漏れた時の最後の壁となる CSP の script-src 許可リストだ。
Security🌿 基礎⏱ 約 7 分2026-09-01

インデックスを貼ったのに、なぜ EXPLAIN は Seq Scan のままなのか?

プランナーはインデックスの有無ではなく候補プランごとの見積りコストで実行計画を選ぶ。ANALYZEが集める統計情報pg_statsから選択率を推定する仕組みと、統計が古いと見積りが実態とズレる理由を解説。
DB🌿 基礎⏱ 約 8 分2026-08-31