本文へスキップ
webkida wiki
戻る

【HTTP】Cookieの属性と送信条件

ブラウザが Cookie を保存し、次のリクエストに載せる条件と、寿命・Domain・Path・Secure・HttpOnly・SameSite の役割を整理する

ログインしたあと、別のページへ移ってもログインは続きます。HTTP は、前のリクエストを覚えていません。続いているように見えるのは、ブラウザがセッションIDを、次のリクエストへ自分で付けているからです。

サーバーは、名前と値をブラウザに預けます。ブラウザは、条件を満たすリクエストにだけ、その名前と値を戻します。

用語

ブラウザが保存する、名前と値の組です。次のリクエストでは Cookie ヘッダに、名前と値だけが戻ります。

サーバーがレスポンスに載せるヘッダです。ブラウザに、名前と値を保存させます。

Set-Cookie: session_id=s-1; Path=/; SameSite=Lax

この例で保存されるのは session_id=s-1 です。次のリクエストで戻るのは Cookie: session_id=s-1 だけで、Path や SameSite は戻りません。この2つは、ブラウザがどのリクエストにその値を付けるかを決めるルールです。

セッションID

ログイン後にサーバーが発行する識別子です。ユーザー情報そのものはサーバー側にあり、付いてきたIDから引きます。

Same-site

Cookie のサイトと、リクエストを始めたサイトが一致していることです。app.example.com と api.example.com は、ホストが違っても同じサイトです。example.com とそのサブドメインで、スキームも同じだからです。https://example.com と http://example.com は別サイトです。

Cross-site

別のサイトを起点に、対象のサイトへリクエストが飛ぶことです。evil.example から bank.example への送信がこれにあたります。

HTTPは前の通信を覚えない

HTTP は、リクエストのたびに独立しています。サーバーは、プロトコルだけでは「さっきの通信の続き」だと分かりません。

Cookie は、その続きをブラウザに持たせるために作られました。ブラウザが次のリクエストへ同じ名前と値を自分で付けるので、サーバーは「さっきと同じブラウザからだ」と判断できます。

1994年、Netscape の Lou Montulli が、買い物かごのようにページをまたいで状態を保つ必要から入れました。ログインは、そのあと特によく使われるようになった場面です。認証が通ったあと、サーバーはセッションIDを Cookie に預けます。次のリクエストでそのIDを受け取り、サーバー側に置いたユーザー状態を引きます。

sequenceDiagram
  participant B as ブラウザ
  participant S as サーバー
  S->>B: Set-Cookie session_id=s-1
  Note over B: 名前と値を保存する
  B->>S: Cookie session_id=s-1
  Note over S: IDからユーザー状態を引く

図: サーバーがセッションIDを預け、ブラウザが次のリクエストに名前と値だけを戻す。

【ログイン直後】サーバー → ブラウザ

サーバー:「セッションIDは s-1。次のリクエストに付けてください」

【次のページ】ブラウザ → サーバー

ブラウザ:「/account へ行きます。預かった session_id=s-1 を付けました」

サーバー:「s-1 はログイン済みのセッションです。このユーザーの状態を返します」

付けるのはブラウザです。ページ上のスクリプトは、このヘッダを組み立てません。

主な用途

預ける中身は、用途で違います。

用途預けるもの状態の置き場所
ログイン状態の維持セッションIDサーバー側。IDから引く
ユーザー設定言語やテーマなど、値そのものCookie の中
トラッキング流入元のIDCookie の中。成約時にサーバーが読む

ユーザー設定は、言語や表示テーマのように、次のリクエストでサーバーが知る値です。

トラッキングでは、紹介リンクに付いた流入元IDを、広告主側が Set-Cookie で預かります。あとで成約のリクエストにブラウザがそれを付けるので、どの紹介元の成果かが分かります。

バックエンドからもフロントからも付けられる

保存したあとの自動送信は、どちらが付けたかで変わりません。

バックエンドは、レスポンスの Set-Cookie で預けます。フロントは、document.cookie への代入で預けます。どちらもブラウザの保存先は同じです。次以降、条件に合うリクエストへ、ブラウザが Cookie ヘッダで付けます。

document.cookie = "theme=dark; Path=/; Max-Age=2592000; SameSite=Lax";

ログイン用だけは、付与をバックエンドに寄せます。HttpOnly は Set-Cookie でしか付けられません。これを付けると、フロントの JavaScript からは読めません。

ブラウザが付ける条件

ブラウザへ残す指示は6つです。リクエストに付けるかは、Domain、Path、期限、Secure、SameSite が見ます。HttpOnly は、付けたまま JavaScript から読めるかを決めます。

属性ブラウザが行う判断
Domainどのホストへ付けるか。未指定なら、セットしたホストだけ
Pathどのパス配下のリクエストに付けるか。未指定なら、セットしたURLのディレクトリ
Expires / Max-Ageいつまで保存し、期限内のリクエストにだけ付けるか
SecureHTTPS のリクエストにだけ付けるか
SameSite別サイトからのリクエストに付けるか
HttpOnlyリクエストへは付けたまま。JavaScript から読めるかを決める

Expires と Max-Age の両方があるときは、Max-Age が優先されます。両方なければ、ブラウザを閉じるまでのセッション Cookie です。閉じたあとも、ブラウザの復元で残ることがあります。

SameSite を付けないとき、いまのブラウザは Lax として扱います。

ログインでは HttpOnly で預ける

ログインでは、サーバーがセッションIDを Set-Cookie し、HttpOnly を付けます。

Set-Cookie: session_id=s-1; Path=/; HttpOnly; Secure; SameSite=Lax

HttpOnly が止めるのは、JavaScript が document.cookie からその値を読むことです。XSS で注入されたスクリプトは、セッションIDを抜き取って外へ送る、という読み取りができません。

ログイン状態は、サーバーがセットしたIDを、ブラウザが戻すだけで足ります。ページ上のスクリプトが、そのIDを読む場面はありません。

別サイトからの送信は SameSite で決まる

SameSite は、別サイトを起点にしたリクエストに、その Cookie を付けるかを決めます。

送信先のホストが条件に合うと、ブラウザは Cookie を自動で付けます。SameSite が別サイトからの送信を許していると、認証 Cookie が付き、サーバーには本人の操作に見えます。これが CSRF です。

【別サイトからの POST】evil.example → bank.example

evil.example:「銀行の振込URLへ、POST を飛ばしたよ」

ブラウザ:「SameSite=Lax だから、別サイトからの POST には session_id を付けない」

bank.example:「Cookie が無い。この振込は受けない」

SameSite=None かつ Secure のときは、この POST にもセッションIDが載ります。銀行側には、ログイン済みユーザー本人からの振込に見えます。

sequenceDiagram
    participant Evil as evil.example
    participant Browser as ブラウザ
    participant Bank as bank.example

    Evil->>Browser: ① POST /transfer を bank へ送らせる
    alt Lax または Strict
        Browser->>Bank: ② Cookie を付けない
    else None
        Browser->>Bank: ② session_id を付ける
    end

図: 別サイトからの POST にセッションIDが付くかは、SameSite で分かれる。

Strict

別サイトを起点にしたリクエストには、Cookie を付けません。リンクのクリック(トップレベルの GET)でも付けません。

検索結果やメールのリンクから銀行を開くと、最初の表示ではログイン済みに見えないことがあります。CSRF には強く、最初の1画面ではログイン状態が落ちて見えます。

Lax

別サイトからは、アドレスバーが切り替わるトップレベルの GET にだけ付けます。リンクをクリックしてページを開く移動です。別サイトの img や iframe、fetch の GET には付けません。別サイトからの POST にも付けません。

同じサイトからの POST には、Strict でも Lax でも付けます。止める対象は、別サイトを起点にしたリクエストです。

外部リンクの最初の表示でログイン状態が見え、別サイトからの POST には付かないので、認証 Cookie では Lax がよく使われます。未指定のときの既定も Lax です。

None

別サイトからでも Cookie を付けます。Secure が必須で、HTTPS のときだけ使えます。

Set-Cookie: session_id=s-1; SameSite=None; Secure

フロントと API が別のサイトにあるとき、外部サービスとの連携、iframe、一部の SSO で使います。Safari など、別サイトの Cookie を最初から受け取らないブラウザでは、None を付けても保存されないことがあります。

SPAで別ホストのAPIを呼ぶとき

画面が https://app.example.com、API が https://api.example.com だと、オリジンは違います。サイトは同じです。example.com のサブドメインで、スキームも https だからです。

SameSite の上では、この fetch は同じサイトです。Strict や Lax でも、API へのリクエストに Cookie を付けられます。SameSite=None が必要になるのは、https://app.example-a.com から https://api.example-b.com のように、サイト自体が違うときです。

sequenceDiagram
    participant App as app.example.com
    participant Browser as ブラウザ
    participant API as api.example.com

    App->>Browser: ① same-site の fetch
    Browser->>API: ② ホストは違うが example.com<br/>スキームが同じなら Cookie を付ける

図: ホストは違っても example.com でスキームが同じなら same-site。Strict や Lax の Cookie が付く。

Cookie を預かるホストは API 側です。Domain を付けなければ、戻るのは api.example.com へのリクエストだけです。画面の app.example.com には付きません。セッションIDは、API が HttpOnly で預ければ足ります。

別オリジンへの fetch は、既定では Cookie を送りません。送るときは credentials を include にします。API は、呼び元のオリジンを Access-Control-Allow-Origin で指名し、Access-Control-Allow-Credentials に true を返します。オリジンの指定にアスタリスクは使えません。

fetch("https://api.example.com/account", {
  credentials: "include",
});
値別サイトから認証での位置
Strict付けない。リンクのクリックも含むセキュリティ優先。最初の画面で未ログインに見えることがある
Laxトップレベルの GET だけ付ける。POST や埋め込みは付けない一般的な選択。未指定の既定
None付ける。Secure が必須別サイトとの通信が必要なとき

まとめ


この記事をシェア:

前の記事
【HTTP】ロングポーリングの流れ
次の記事
【Feature Flag】デプロイとリリースを分ける