Cookieについて解説

## Cookieとは

本来ステートレスなhttpアクセスをステートフルにしてくれる便利な仕組みのことです。  
認証情報やショッピングカートの情報をブラウザ側で保持するために使われたりします。  
Cookieという名前の由来は諸説あるようですが、ブラウザに保存される小さなデータ片のようなものだと思っておけばよいです。おしゃれな名前ですね。

## どのように使われるのか

Cookieの扱いは以下で規定されています。

* RFC6265 ([https://tex2e.github.io/rfc-translater/html/rfc6265.html](https://tex2e.github.io/rfc-translater/html/rfc6265.html), 日本語訳が付いているので読みやすい)  
* RFC6265bis([https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-14.html](https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-14.html), RFC6265の改訂版)


細かいことを色々すっ飛ばすと、ブラウザ視点での大まかな流れは以下になります。

1. リクエストを投げる
2. レスポンスにSet-Cookieヘッダが含まれていたら、値をCookieストアに保存
3. 以降のリクエストでは保存済みの値をCookieヘッダに設定して送信

簡単ですね。  
次はもう少し細かい仕様について見ていきましょう。

## Set-Cookieヘッダの仕様について

Set-Cookieヘッダのフォーマットは以下になります。  
`Set-Cookie: {Cookie名}={Cookie値}; {メタデータ}; {メタデータ}; {メタデータ}; ...`

Cookie名とCookie値は保存したいデータの名前と値です。
Set-Cookieヘッダの先頭に好きな名前と値をイコールで繋ぎ、セミコロンで閉じましょう。

メタデータは主にCookieの制約に関するデータです。

### メタデータに指定できる属性について
#### **Expires**
有効期限（UTC時刻）を指定します。未指定の場合、ブラウザを閉じると削除されます。
**例**: `Expires=Wed, 21 Oct 2025 07:28:00 GMT`  
**ベストプラクティス**: セッション管理に使う場合は指定しないこと[^expired]。

#### **Max-Age**
有効期間を秒単位で指定します。未指定の場合、ブラウザを閉じると削除されます。
`Expires` と `Max-Age` の両方が指定された場合、`Max-Age` が優先されます。  
**例**: `Max-Age=3600`  
**ベストプラクティス**: `Expires` と同じ。

#### **Domain**
Cookie が有効化されるドメインを指定します。デフォルトでは現在のホストが適用されます。  
Cookie 名に `__Host-` プレフィックスが付いている場合は指定できません（後述）。  
**例**: `Domain=example.com`  
**ベストプラクティス**: 指定しない[^domain]。

#### **Path**
Cookie が有効化されるパスを指定します。
Cookie 名に `__Host-` プレフィックスが付いている場合は `Path=/` を指定しなければなりません（後述）。  
**例**: `Path=/account`  
**ベストプラクティス**: できるだけ狭いパスを指定する[^path]。

#### **Secure**
HTTPS 接続時のみ Cookie を送信する場合に指定します。
Cookie 名に `__Host-` または `__Secure-` プレフィックスが付いている場合は指定しなければなりません（後述）。  
**例**: `Secure`  
**ベストプラクティス**: 指定する[^secure]。

#### **HttpOnly**
JavaScript からのアクセスを禁止する場合に指定します。  
**例**: `HttpOnly`  
**ベストプラクティス**: 指定する[^httponly]。

#### **SameSite**
クロスサイトリクエストでの Cookie 送信を制御する場合に指定します。  
RFC6265bis で追加されました。  
eTLD+1 [^eTLD1]（.co.jpや.comなどに登録できるドメイン名）が同じならSame Site、そうでないならCross Siteです。 

- `Strict`: Same SiteにだけCookieの送信を許可する  
- `Lax`: Cross SiteにはGETリクエストのみ許可する  
- `None`: Cross Siteでも制限なし  

デフォルト値は `Lax`です。  
**例**: `SameSite=Strict`  
**ベストプラクティス**: ユーザーアクションに関連する場合は `Strict`。それ以外の場合は指定しない[^samesite]。  

[^eTLD1]: https://developer.mozilla.org/en-US/docs/Glossary/eTLD
[^samesite]: https://web.dev/articles/samesite-cookies-explained?hl=ja
[^secure]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#secure-attribute
[^httponly]:https://web.dev/articles/samesite-cookies-explained?hl=ja
[^domain]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#domain-and-path-attributes
[^path]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#domain-and-path-attributes
[^expired]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#expire-and-max-age-attributes
----
歴史的な経緯からか、属性名にKebab-CaseとSnakeCaseが混ざっていて一貫性がないですね。

ブラウザからリクエストを送信する際には、上述したメタデータの制約を満たすCookie名とCookie値のペアをリストアップして、Cookieヘッダに詰め込みます。

## Cookie名のプレフィックスについて

なんとCookie名につけたプレフィックスによってメタデータに設定できる値が制約されます。

* `__Host-{Cookie名}`: Domainは指定不可。Pathには`/`を指定しなければならない。Secureも指定しなければならない。  
* `__Secure-{Cookie名}`: Secureを指定しなければならない

~~ややこしいですね。~~ これにはメタデータの誤設定や攻撃者によるCookieの上書きを防ぐ効果があります。
`__Host-`プレフィックスを付けられる場合は付けて、それ以外の場合は`__Secure-`プレフィックスを付ける事が推奨されています[^cookieprfix]。
[^cookieprfix]: https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies#name

## Q. JS側で同じような機能を実装できるんじゃないの?

A. 可能です。でもブラウザに搭載されているせっかくの便利機能なので使いましょう。JavaScriptから隔離された状態でデータを扱えるのでセキュアです。

## Q. Cookieレスって聞いたことあるけど、そのうちCookieの知識は不要になる?

A. なりません。紛らわしいですが、CookieレスとはブラウザによるサードパーティCookie(Cross Siteに送信されるCookie)の制限や廃止の動きを指します。  
   Web広告業界ではCookieに頼らない実装方式への転換が求められているという背景があるため、Cookieレスという言葉が使われているようです。  
   ファーストパーティCookie(Same Siteに送信されるCookie)は今後も使われ続けるでしょう。

## (おまけ) Cookieの実体

Windows版のChromeを例に挙げると、`C:\Users\{ユーザー名}\AppData\Local\Google\Chrome\User Data\Default\Cookie` にsqliteファイルとしてCookieのデータが保存されています。
sqlite3コマンドなどで普通に開けます。  
ブラウザ側で保持する情報は仕様で定められていますが、カラム名や保存形式に関する指定は無いため、ブラウザごとに自由に実装されているようです。  
例えばSQLiteテーブルのカラム名はブラウザごとに異なっていますし、ChromeだとCookie値は暗号化されていますが、FireFoxでは平文のままだったりします。不思議ですね。