仕組みから学ぶ Web
基礎読了目安 14#Cookie#セッション#認証#SameSite

Cookie とセッション — 状態を持たない HTTP に記憶を持たせる

サーバーが覚えるか、利用者に持たせるか。この選択が、ログアウトの実装可能性とスケール特性をすべて決める。

この記事の進み方

What — セッションはどこに置かれているか

HTTP は状態を持ちません。1回目のリクエストと2回目のリクエストは、サーバーから見れば無関係な他人です。「ログイン中」という状態を成立させるには、毎回何かを持たせる必要があります。

Figureログインからログアウトまで
ブラウザアプリサーバーセッション保存先Redis / DBPOST /login(ID とパスワード)セッションを作成して保存Set-Cookie: sid=xxx; HttpOnly; SecureGET /mypage(Cookie: sid=xxx)sid から中身を引くその人向けのページPOST /logoutセッションを削除
1/8
POST /login(ID とパスワード)認証情報を送る。ここだけは必ず HTTPS でなければならない。

Cookie に入っているのはセッション ID だけで、中身はサーバーが持つ。だからサーバー側で1行消せば、その瞬間にログアウトが成立する。

HttpOnly
JavaScript から読めなくする。XSS が起きても Cookie を盗み出せない。認証用 Cookie には必須。
Secure
HTTPS の通信でしか送らない。付けないと、平文通信に紛れ込んだ瞬間に盗聴される。
SameSite=Strict
他サイトからの遷移では一切送らない。安全だが、外部リンクから来るとログアウト状態に見える。
SameSite=Lax
他サイトからのリンク遷移(GET)では送るが、POST では送らない。現在の多くのブラウザの既定値。
SameSite=None
他サイトからでも送る。Secure が必須。埋め込みウィジェットなど、明確な理由があるときだけ。
Domain / Path
送信先の範囲。Domain を広げるとサブドメイン全体に送られる。広げすぎると、1つのサブドメインの脆弱性が全体に波及する。
Max-Age / Expires
有効期限。指定しないとブラウザを閉じると消える(セッション Cookie)。
Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600

この4つの属性を書かない理由は、基本的にありません。

サーバーが覚えるか、利用者に持たせるか

Compare2つの方式の性質

ID だけを渡し、中身はサーバーが持つ。サーバーが状態を持つ。

Cookie: sid=9f2a...

サーバー側:
9f2a... → { userId: 42, role: "admin", ... }
即時ログアウト
できる
権限変更の反映
即座に効く
毎リクエストの参照
必要
サーバーの状態
持つ(共有が必要)
Cookie のサイズ
数十バイト
サイズ
  • サーバーを複数台にすると、セッションを共有する仕組み(Redis など)が要る。
  • 退会・アカウント停止・権限剥奪をその場で反映できるのが最大の利点。

どちらが優れているかではなく、即時失効が必要か、状態を持たせたくないかで決まる。多くのサービスでは、この2つを組み合わせる。

Cookie が発明されたのは1994年、ネットショッピングのカートを実現するためでした。「前のページで何を入れたか」をサーバーが思い出せないと、カートが成立しません。

このとき選ばれたのが「ブラウザが勝手に付けて送る」という設計です。ページを書く人が毎回コードを書く必要がなく、フォーム遷移でもリンク遷移でも画像の読み込みでも、自動的に状態が引き継がれます。

この自動性が、利便性と脆弱性の両方を生みました。

  • 便利: ログイン状態がタブをまたいでも維持される
  • 危険: 攻撃者のサイトからのリクエストにも、自動で付いてしまう

後者が CSRF です。攻撃者のページに <form action="https://bank.example.com/transfer" method="POST"> を置いて自動送信すれば、ブラウザは律儀にあなたの Cookie を付けて送ります。

SameSite 属性は、この自動送信を「どこから来たリクエストか」で制限するために、20年以上経ってから追加された対策です。

SameSite=Lax だけで CSRF は防げるか

現在の多くのブラウザは SameSite の既定を Lax にしました。これにより、他サイトからの POST では Cookie が送られません。CSRF の大部分はこれで塞がります。

ただし「防げた」と言い切れない理由が3つあります。

  1. GET で状態を変えている場合は素通り。 Lax はリンク遷移(GET)では Cookie を送ります。GET /delete?id=1 のような設計だと防げません。
  2. 古いブラウザや一部の環境では既定が異なる。 明示的に指定しない限り、挙動は保証されません。
  3. 同一サイト内からの攻撃には無力。 XSS があれば、そのページ自身からリクエストできるので SameSite は関係ありません。
確認 — ここまで読めたか

サーバーが覚える方式(セッション)と、利用者に持たせる方式(トークン)。ログアウトを即座に効かせやすいのはどちらですか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この要件で、認証の保持方式と Cookie の属性をどう設計しますか?

与えられた条件
  • BtoC のサービス。Web とスマホアプリの両方から使う
  • Web は https://app.example.com、API は https://api.example.com(別オリジン)
  • 不正利用が疑われるアカウントを、運営が即座に強制ログアウトできる必要がある
  • ユーザーは決済情報を登録するため、権限変更は即時反映したい
  • ログイン状態は2週間維持したい(毎回ログインさせたくない)
  • 外部サイトからの流入が多く、リンクを踏んで来たときにログイン状態が見えていてほしい
この軸で考える
  • · 即時強制ログアウトという要件が、方式の選択にどう効くか
  • · 別オリジン構成で Cookie を使うとき、どの属性が必要になるか
  • · 「2週間維持」と「即時失効」は両立するか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「セッション Cookie の属性が付いていない」という診断結果を、開発チームに説明してください

与えられた条件
  • 相手は動いているので問題ないと考えている
  • 社内には HTTP のままの内部ツールがある
  • 3つの属性それぞれの必要性を納得してもらいたい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。

この記事を前提にしている記事