Cookie
Cookie ・ くっきー
サーバがブラウザに預け、条件に合うリクエストへ自動で付けて返させる小さなデータ。
✦ 30秒で試す — 航海ボタンを押していないのに、ログインの印が勝手に送られるDomain=shop.example.com と書くと、印が付く範囲は?予想してみる概要
Cookie は、サーバがブラウザに預ける小さなデータ(名前と値の組)です。サーバがレスポンスで「これを保存しておいて」と渡すと、ブラウザはそれを保存し、以後その Cookie の条件に合うリクエストへ毎回自動で添えて送り返します。一度ログインすればページを移動してもログイン状態が続くのは、この仕組みのおかげです。
HTTP は本来、1回のリクエストとレスポンスで完結し、サーバは前回の相手を覚えていません(ステートレス)。Cookie はこの性質を変えずに、「覚えておくべきことをブラウザに持たせ、毎回提示してもらう」ことで状態を実現します。ログイン状態の維持とセッションの識別、言語や表示の設定、そしてアクセス解析や広告のトラッキングまで、Web の「あなたを覚えている」体験のほとんどの裏に Cookie がいます。
大事なのは、Cookie を付けるかどうかを決めるのがブラウザだという点です。サーバは「どの宛先・どのパスに、どんな条件で付けてよいか」を属性で指示するだけで、実際にリクエストへ載せるのはブラウザの仕事です。この「持ち主が意識しなくても勝手に付く」性質が、Cookie の便利さの源であり、同時に多くの攻撃の入口にもなっています。
なぜ生まれたか
1994年、Netscape 社のエンジニアだったルー・モントゥリは、EC サイトの「買い物かご」を実装するという課題に直面していました。ステートレスな HTTP では、商品をかごに入れた人と、次のページを開いた人が同一人物だとサーバには分かりません。識別子を URL に埋め込む方法もありましたが、リンクを共有したりブックマークしたりすると他人と同じかごを覗けてしまい、ページを1つ移動するたびに全リンクを書き換える必要もありました。
そこで考案されたのが、「サーバが渡した小さなデータを、ブラウザが同じサイトへのリクエストに自動で付け返す」という仕組みでした。名前は、プログラム間で受け渡す小さな合言葉を指す既存の用語「magic cookie」に由来します。仕様はのちに RFC 2109(1997年)、RFC 6265(2011年)として標準化されました。ログインも EC も成立させたこの発明は、一方でサイトを横断してユーザーを追跡する手段にもなり、自動で付く性質を悪用する攻撃も生みました。その後に足された属性の多くは、この2つの副作用への対策の歴史です。
詳細
Set-Cookie と Cookie ヘッダの往復
仕組みの正体は、たった2つの HTTP ヘッダです。サーバはレスポンスに Set-Cookie: sid=a1b2; Path=/; Secure; HttpOnly; SameSite=Lax を付けて保存を指示し、ブラウザは以後の条件に合うリクエストに Cookie: sid=a1b2 を自動で付けます。送り返されるのは名前と値だけで、属性はブラウザが「付けるかどうか」を判断するためにだけ使います。
重要なのは「ブラウザが自動で送る」という点です。アプリのコードが何もしなくても、条件を満たすリクエストには Cookie が付いて飛んでいきます。リンクをたどったときも、画像を読み込むときも、ほかのサイトに置かれたフォームから送信されたときもです。
どのリクエストに付くか — Domain・Path・Secure
Cookie の効く範囲は属性で決まります。Domain を付けないと、その Cookie は発行したホストだけに送られます(host-only)。Domain=shop.example.com を付けると、blog.shop.example.com のようなサブドメインにも送られるようになります。直感とは逆に、Domain は「付けると広がる」属性です。Path=/account を付けると /account 以下のパスへのリクエストにだけ付き、Secure を付けると HTTPS のリクエストにだけ付いて、平文の http では送られません。
有効期限は Expires(日時)か Max-Age(秒数)で決めます。どちらも指定しなければ、ブラウザを閉じると消える「セッション Cookie」になります(ただし最近のブラウザは「前回のタブを復元」でセッション Cookie も残すことがあります)。範囲は必要最小限に、寿命は用途に応じて、が原則です。
SameSite — 「どのページから出たか」で付けるかを変える
Domain・Path・Secure が「宛先」を見るのに対し、SameSite属性は「リクエストを送り出したページがどのサイトか」を見ます。ここでいうサイトは example.com のような登録可能ドメインの単位で、blog.shop.example.com と shop.example.com は同じサイトです。Strict は別サイトから始まったリクエストに一切付けず、Lax は別サイトからのリンクをたどる GET(トップレベルの遷移)にだけ付け、None はどこから来ても付けます(None には Secure が必須で、付けないとブラウザは保存しません)。
SameSite が生まれたのは、自動で付く性質を悪用する CSRF への対策です。罠ページに置いたフォームから銀行へ POST させると、ブラウザは銀行の Cookie を律儀に付けてしまい、銀行からは本人の操作に見えます。Lax なら、この別サイト発の POST には Cookie が付きません。
注意したいのは、SameSite を指定しないときの扱いがブラウザごとに違うことです。Chrome は2020年(Chrome 80)から、指定のない Cookie を Lax とみなすようになり、Edge など Chromium 系のブラウザも同じです(ただし発行から2分以内の Cookie はトップレベルの POST にも付ける例外があります)。Firefox は同じ変更を試験的に有効にしたものの、互換性の問題で見送りました。Safari も採用しておらず、どちらも指定がなければ None と同じ扱いです。「今のブラウザは既定で Lax だから安全」とは言えないので、セッションを運ぶ Cookie には SameSite を必ず明示します。
HttpOnly と接頭辞 — 読まれない・上書きされない
HttpOnly を付けると、JavaScript の document.cookie から読めなくなります。XSS でスクリプトを差し込まれても、セッション ID を外へ持ち出されるのは防げます(ただし、差し込まれたスクリプトがそのページからリクエストを送れば Cookie は自動で付くので、XSS そのものの被害は防げません)。
名前の先頭に付ける接頭辞でも、ブラウザに条件を強制できます。__Secure- で始まる Cookie は Secure がないと保存されません。__Host- で始まる Cookie は、さらに Domain を付けず Path=/ であることが求められます。これで、サブドメインや平文の http から同じ名前の Cookie を上書きされる攻撃(Cookie トッシング)を防げます。セッション ID には __Host-sid=…; Path=/; Secure; HttpOnly; SameSite=Lax が今日の堅い形です。
下の部品で、Set-Cookie の属性を押して切り替えながら、3つのタブのページからリクエストの封筒を送ってみてください。封筒がブラウザの Cookie 入れを通るときに、Cookie が付くかどうかと、その理由が分かります。
「shop.example.com」のタブにあるマイページへをつかんで、右の shop.example.com のサーバへ投げ込んでください。タップするとおまかせで送ります。
セッション Cookie という代表的な使い方
もっとも典型的な用途はセッション管理です。ログインに成功するとサーバは推測できないランダムなセッション ID を発行して Cookie で渡し、サーバ側でその ID と利用者の情報を結び付けて保管します。以後は Cookie の ID を見るだけで「誰のリクエストか」が分かります。Cookie 自体には ID しか入っておらず、実体はサーバ側にあるのがポイントです。対照的に、JWT のように署名付きのデータそのものを Cookie に入れ、サーバ側に状態を持たない方式もあります。認証後の状態をどちらで保つかは、Web アプリ設計の定番の分かれ道です。
サードパーティ Cookie とプライバシー
表示中のページとは別のサイトに送られる Cookie を「サードパーティ Cookie」と呼びます。多くのサイトに同じ広告配信ドメインの広告枠やタグが埋まっていると、そのドメインの Cookie は、どのサイトで読み込まれても同じ値で付きます。広告配信側は「この ID の人は、ニュースサイトの次にショップを見た」とサイトをまたいで行動をつなげられます。これが長年ターゲティング広告を支えてきた仕組みです。
ブラウザの対応は分かれています。Safari は2020年からサードパーティ Cookie を既定ですべてブロックし、Firefox は2022年から「包括的 Cookie 保護」で、埋め込まれた先の Cookie をトップレベルのサイトごとに別の入れ物へ分けています(サイトが変わると別人に見えるので、追跡がつながりません)。Chrome は2020年に廃止を予告したあと延期を重ね、2024〜25年に廃止の計画そのものを撤回しました。既定では今も許可しており、ブロックするかは利用者の設定に委ねています。GDPR などの規制で広まった「Cookie 同意バナー」も、この追跡への対応です。
追跡ではない正当な埋め込み(地図やチャットのウィジェットなど)のために、Partitioned 属性(CHIPS)も用意されました。この属性の Cookie は「埋め込まれたトップレベルのサイトごと」に分けて保存されるので、埋め込み先ごとの状態は保ちつつ、サイトをまたいだ追跡には使えません。「状態を保つためのファーストパーティ Cookie」と「追跡のためのサードパーティ Cookie」は、同じ技術のまったく別の顔です。
実務での落とし穴
Cookie には1つあたり約4KB というサイズの上限があり、しかも対象のリクエストすべてに毎回載って往復します。大きなデータを詰め込むと通信を無駄に太らせるので、ブラウザの中だけで使う値なら localStorage などの Web Storage が向いています(ただし Web Storage には HttpOnly がなく XSS で読まれるため、セッション ID の置き場としては Cookie に分があります)。
設定ミスの定番は、Domain を広く取りすぎて、管理の甘いサブドメインにまでセッションを配ってしまうこと、有効期限を長くしすぎて盗まれたときの被害の窓を広げること、そして SameSite の既定値に頼って CSRF 対策を省くことです。「誰に(Domain・Path)・どんな経路で(Secure)・どこから来たときに(SameSite)・いつまで(Max-Age)付けるか」を1つずつ意識して設計することが、Cookie を安全に使う鍵になります。
試問
あなたのブラウザには3つのタブ(shop.example.com・そのサブドメインの blog・別サイトの news.example)があり、各ページのボタン(リクエストの封筒)をサーバへ送れます。shop が預けた Set-Cookie の属性は、押すと切り替わります。いまの設定は SameSite=None で Secure なしです。
「shop.example.com」のタブにあるマイページへをつかんで、右の shop.example.com のサーバへ投げ込んでください。タップするとおまかせで送ります。
