
Cookie・セッション・SameSite の基礎 - Secure / HttpOnly と CSRF・CORS の関係
ログイン状態の維持、CSRF対策、クロスオリジンでの認証——どれも根っこは Cookie・セッション・SameSite の理解です。ここが曖昧だと「なぜかログインが切れる」「なぜかCookieが送られない」で延々ハマります。この記事では、MDN・RFC 6265bis・OWASP を一次ソースに、Web認証の基礎を整理します。
Cookieの基本
サーバーはレスポンスの Set-Cookie ヘッダでCookieを設定し、ブラウザは以降のリクエストに Cookie ヘッダで自動送信します。1つのレスポンスに Set-Cookie を複数並べれば複数のCookieを設定できます。
Set-Cookie: sessionid=abc123
Set-Cookie: prefs=dark; Max-Age=2592000有効期限とスコープ
Expires: 絶対日時で失効を指定Max-Age: 相対秒数で指定。両方あればMax-Ageが優先- どちらも無ければセッションCookie(ブラウザ終了で消える。ただし「セッション復元」機能で残ることがある)
Domain: 省略するとそのホストのみ。Domain=example.comを付けるとサブドメインにも送られる(意図せず広げる事故が多い)Path: パスのプレフィックス一致。なおPathはセキュリティ境界ではない(同一オリジンの別パスからJSでアクセス可能)
セキュリティ属性: Secure と HttpOnly
| 属性 | 効果 | 防ぐ脅威 |
|---|---|---|
Secure | HTTPS接続でのみ送信 | 通信経路上の盗聴 |
HttpOnly | document.cookie から読めない | XSSによるCookie窃取 |
重要なのは、HttpOnly でもHTTPリクエストには送られること。fetch の通信にCookieは付きますが、JSから値を読むことだけが封じられます。認証用Cookieは原則 Secure; HttpOnly を付けます。
SameSite: Strict / Lax / None
SameSite は「クロスサイトのリクエストでCookieを送るか」を制御します。CSRF対策の要です。
| リクエストの種類 | Strict | Lax | None |
|---|---|---|---|
| 同一サイト | 送信 | 送信 | 送信 |
| 別サイトからのリンク(GETナビゲーション) | ブロック | 送信 | 送信 |
別サイトからの POST フォーム | ブロック | ブロック | 送信 |
別サイトからの fetch / XMLHttpRequest | ブロック | ブロック | 送信 |
別サイトの img / script 読み込み | ブロック | ブロック | 送信 |
Strict: クロスサイトでは一切送らない(最も安全。ただし外部リンクから来ると未ログイン扱いになる)Lax: トップレベルのGETナビゲーション(アドレスバーが変わる遷移)だけ許可。大半のCSRFを緩和しつつ実用的None: クロスサイトでも送る。Secureが必須(SameSite=None単体はブラウザに拒否される)
NOTE
Chrome 80(2020年2月)以降、SameSite を明示しないCookieは Lax 扱いになりました。挙動をブラウザ既定に委ねず、常に明示するのが安全です。
# 認証セッション(推奨形)
Set-Cookie: session=38afes7a8; Secure; HttpOnly; SameSite=Strict; Path=/
# サードパーティ埋め込み等でクロスサイト送信が要る場合
Set-Cookie: widget=7yjgj; Secure; HttpOnly; SameSite=Noneセッション管理
典型的なサーバー側セッションは、セッションIDだけをCookieで渡し、本体(ユーザー情報など)はサーバー側ストア(DB・Redis等)に置きます。
- セッションIDは予測不可能な値(CSPRNGで十分なエントロピー)にする
- ログイン成功時にセッションIDを再生成する——これがセッション固定攻撃の根本対策。
SecureやHttpOnlyを付けるだけでは固定攻撃は防げません - セッション認証情報を
localStorage等のWeb Storageに置かない(XSSで抜かれる)
トークンベース(JWTなど)との使い分けはJWT の仕組みと正しい使い方を、パスワードに依存しない認証はパスキー / WebAuthnもどうぞ。
CSRFとの関係(SameSiteだけでは不十分)
SameSite=Strict/Lax はクロスサイトからの自動Cookie送信を抑えるので、CSRFを大きく緩和します。ただしこれ単体では守りきれません。
Laxは安全なGETを通す: GETで状態を変更するAPIがあると素通りする- サブドメインは同一サイト扱い:
evil.example.comを乗っ取られるとapp.example.com宛に送られうる - 古い/組み込みブラウザが
SameSiteを無視することがある
結論として、SameSiteは多層防御の一層。状態変更は CSRFトークン(またはDouble Submitパターン)と併用し、GETで副作用を起こさない設計を守ります。
CORSとCookieの関係
クロスオリジンの fetch でCookieを送受信するには、4つすべてが必要です(CORS の仕組みの続きとして重要)。
| 条件 | 設定場所 |
|---|---|
credentials: "include" | クライアント(fetch) |
Access-Control-Allow-Credentials: true | サーバー応答 |
Access-Control-Allow-Origin: <具体的オリジン> | サーバー応答(* 不可) |
SameSite=None; Secure | Set-Cookie 側 |
どれか1つ欠けても送られません。とくに「クライアントで include にしたのにサーバー側の Allow-Credentials を忘れる」「Allow-Origin: * のまま」という取りこぼしが定番です。
Cookieプレフィックス: __Host- と __Secure-
名前の先頭に特定の接頭辞を付けると、ブラウザが属性の条件を強制します(RFC 6265bis)。
| プレフィックス | 強制される条件 | 意味 |
|---|---|---|
__Secure- | Secure 必須・HTTPS必須 | Secureで設定されたことを保証 |
__Host- | Secure 必須・Domain 指定禁止・Path=/ 必須 | オリジンに固定する最も厳しい設定 |
Set-Cookie: __Host-SID=<token>; Secure; HttpOnly; SameSite=Strict; Path=/認証Cookieには __Host- を使うと、Domain偽装やHTTP経由の上書きを構造的に防げます。
よくある落とし穴
Domainの付け忘れ/付けすぎ: 未指定はホスト限定、指定はサブドメインに拡散。意図と逆になりがちSameSite=NoneにSecureなし: 拒否される。HTTP環境ではNoneは使えない- セッション固定をCookie属性で防ごうとする: 防げない。ログイン後のID再生成が必須
- CORSの認証条件を一部だけ満たす: 4条件は全部そろえる
Allow-Origin: *と credentials の併用: ブラウザが拒否。具体的オリジンを返す
まとめ
- Cookieは
Set-Cookieで設定し自動送信。Domain未指定はホスト限定、Max-AgeがExpiresに優先 - 認証Cookieは
Secure; HttpOnly。HttpOnlyでも通信には乗る(JSが読めないだけ) SameSiteは Strict/Lax/None。NoneはSecure必須。Chrome 80以降は未指定=Lax- セッションはIDだけCookie・本体はサーバー。ログイン後にID再生成で固定攻撃を防ぐ
- SameSiteだけでCSRFは防ぎきれない。トークンと併用。クロスオリジンCookieはCORSの4条件
- 認証Cookieは
__Host-接頭辞でさらに堅く
属性ひとつの意味を押さえるだけで、「ログインが切れる」「Cookieが送られない」の大半は理由が見えてきます。既定に委ねず、明示して堅く設定するのが要です。


