Cookie の扱いとセキュリティについて

# HTTP Cookie

- **Cookie（クッキー / Web Cookie / ブラウザ Cookie）** とは、サーバーがユーザーの Web ブラウザに送信する小さなデータ。
- HTTP プロトコルは [ステートレス](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview#http_is_stateless_but_not_sessionless) だが、 Cookie を使用することで、Web アプリケーションは限られた量のデータ（= 状態）を記憶することができる。

## クッキーの用途

1. **セッション管理**
    - ユーザーのログイン状態を維持する
    - ショッピングカートの内容を保存する
    - ゲームのスコアを保持する
1. **パーソナライズ**
    - ユーザーの言語やUI設定を保持する
1. **トラッキング**
    - ユーザー行動を記録・分析し、広告の表示回数をカウントする

## ログインの流れ

1. 「ブラウザ」は、「ユーザー」がフォームに入力したログイン情報（e.g. メールアドレス、パスワード）を「サーバー」に送信する。
1. 「サーバー」は受け取ったログイン情報が正しい場合、UI をログイン後のものに更新し、「ブラウザ」に **セッションIDを持つ Cookie** を返す。
1. 「ユーザー」が同じサイト内の別ページに移動すると、「ブラウザ」は、**セッションIDを持つ Cookie** と一緒に「サーバー」にリクエストを送信する。
1. 「サーバー」はセッションID をチェックし、以下のいずれかの結果を返す。
    1. セッションID が有効である場合、「サーバー」は「ブラウザ」上に新しいページを返す。
    1. セッションID が有効でない場合、「サーバー」はセッション ID を削除し、「ブラウザ」上に「アクセスが拒否されました」というメッセージを表示し、「ユーザー」に再ログインを求める。

![cookie-basic-example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54467/653e5fd6-b49f-de41-e1a1-985eeb53159d.png)

## Cookie 以外のクライアントストレージ

- Webの初期の頃は、クライアント（ブラウザ）側でデータを保存するためには Cookie を使用するしかなかった。
- 現在では、以下のような最新のストレージ API が推奨される。
    - [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
        - **localStorage**
            - ブラウザを閉じてもデータを保持することができる
        - **sessionStorage**
            - ブラウザを閉じるとデータが消える
    - [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
        - 大容量の構造化データを保存・管理するためのデータベース

### Cookie のデメリット

- **容量と数の制限**
    - ブラウザでは、ドメインごとに保持できる「Cookie」の数は **数百** 程度で、1つあたりの最大サイズは **4KB** 。
    - 一方、Web Storage API や IndexedDB では、より大量のデータを保存できる。
- **パフォーマンスの低下**
    - Cookie はリクエストごとに送信されるため、Cookie の数が多い場合はパフォーマンスが低下する可能性がある

:::note warn
注: ブラウザに保存された Cookie やその他のストレージを確認するには、以下を使用する。
- Firefox Developer Toolsの [Storage Inspector](https://firefox-source-docs.mozilla.org/devtools-user/storage_inspector/index.html)
- Chrome DevToolsの [Application Pannel](https://developer.chrome.com/docs/devtools/progressive-web-apps?hl=ja)
:::

## Cookie の作成、削除、更新

### `Set-Cookie` レスポンスヘッダー

- サーバーは、HTTPリクエストを受信すると、1つ以上の[`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)ヘッダーを含めてレスポンスを送信することができる。
- これにより、 **ブラウザに Cookie を設定する** ことができる。

`Set-Cookie` ヘッダーは、個別の Cookie を「名前」と「値」のペアで設定する。

```
Set-Cookie: <cookie-name>=<cookie-value>
```

ブラウザに2つの Cookie を保存するためのレスポンスの例:

```http
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=chocolate
Set-Cookie: tasty_cookie=strawberry

[page content]
```

:::note warn
注: 各サーバーサイド言語・フレームワークの `Set-Cookie` ヘッダーの使用方法を確認してください。

#### [Ruby on Rails](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html)

- リクエストからの参照:
```rb
class SessionsController < ApplicationController
  def show
    session_id = cookies.encrypted[:session_id]
    puts session_id # => abc123def456
    render plain: "Check"
  end
end
```

- レスポンスへの設定:

```rb
class SessionsController < ApplicationController
  def create
    cookies.encrypted[:session_id] = {
      value: 'abc123def456',
      path: '/',
      httponly: true,
      secure: true,
      same_site: :strict,
      expires: 1.hour.from_now
    }
    render plain: "OK"
  end
end
```

#### [Python](https://docs.python.org/3/library/http.cookies.html)

- リクエストからの参照:
```py
def check_cookie_view(request):
    session_id = request.COOKIES.get('session_id')
    print(session_id)  # => abc123def456
    return HttpResponse('Check')
```

- レスポンスへの設定:

```py
from django.http import HttpResponse

def set_cookie_view(request):
    response = HttpResponse('OK')
    response.set_cookie(
        'session_id',
        'abc123def456',
        max_age=3600,
        secure=True,
        httponly=True,
        samesite='Strict',
        path='/'
    )
    return response
```


#### [Node.js]([Node.js](https://nodejs.org/docs/latest-v19.x/api/http.html#responsesetheadername-value))（Hono）

- リクエストからの参照 / レスポンスへの設定:

```ts
import { Hono } from 'hono'
import { getCookie, setCookie } from 'hono/cookie'

const app = new Hono()

// リクエストからの参照
app.get('/check', (c) => {
  const sessionId = getCookie(c, 'session_id')
  console.log(sessionId) // => abc123def456
  return c.text('Check')
})

// レスポンスへの設定
app.get('/', (c) => {
  setCookie(c, 'session_id', 'abc123def456', {
    path: '/',
    httpOnly: true,
    secure: true,
    sameSite: 'Strict',
    maxAge: 3600
  })
  return c.text('OK')
})


export default app
```

#### Go

- リクエストからの参照:

```golang
func checkHandler(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session_id")
	if err == nil {
		println(cookie.Value) // => abc123def456
	}
	w.Write([]byte("Check"))
}
```

- レスポンスへの設定:

```golang
package main

import (
	"net/http"
	"time"
)

func handler(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     "session_id",
		Value:    "abc123def456",
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteStrictMode,
		MaxAge:   3600,
	})
	w.Write([]byte("OK"))
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":3000", nil)
}
```
:::

### `Cookie` リクエストヘッダー

- ブラウザは、HTTPリクエストを送信するとき、現在のドメインでブラウザに保存されている Cookie を [`Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie) ヘッダー内に含め、サーバーに送り返す。

```http
GET /sample_page.html HTTP/2.0
Host: www.example.org
Cookie: yummy_cookie=chocolate; tasty_cookie=strawberry
```

### Cookie の削除

- Cookie の削除は、**`Expires` 属性** の値を過去に設定するか、 **`Max-Age` 属性**を 0 に設定することによって実現できる。
- Cookie の作成時に `Set-Cookie` ヘッダー内にこれらの属性を設定するかどうかによって、
「**永続的な Cookie**」または「**セッション Cookie**」に分類される。

#### **永続的な Cookie**

- `Expires` 属性または`Max-Age` 属性で、指定された期限以降に削除される Cookie のこと

##### `Expires` 属性

```
Set-Cookie: id=a3fWa; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
```

##### `Max-Age` 属性

```
Set-Cookie: id=a3fWa; Max-Age=2592000
```

:::note warn
注: 
- `Expires` 属性は特定の日時を指定する。
- `Max-Age` 属性は現在時刻からの秒数を指定する。
- 両方が設定されている場合は `Max-Age` 属性が優先される。
    - この理由は、 `Expires` 属性は、Cookie が設定されたクライアントに対して相対的な日時であるため。
    - もしサーバーが異なった日時を指定していると、　`Expires` 属性ではエラーが発生する可能性がある。
:::

#### セッション Cookie

- `Expires` 属性または `Max-Age`属性がない Cookie のこと
- **現在のセッションが終了すると削除される**
    - 一般には、ブラウザを閉じると削除
    - 「現在のセッション」が終了するタイミングは、「ブラウザ」によって定義される。
    - Chromeなど一部のブラウザは、再起動時にセッション復元機能を提供している。
    - これにより、セッション Cookie がいつまでも有効な状態になってしまう問題が発生する可能性がある。

:::note warn
注: 
- サイトがユーザーを認証する場合は、ユーザーが認証するたびに、（すでにセッション Cookie が存在していたとしても）セッション Cookie を再作成し、再送信する必要がある。
- これにより、第三者がユーザーのセッションを再利用できる [**セッション固定攻撃**](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_fixation) を防ぐことができる。
:::

#### ゾンビ Cookie / スーパークッキー

- ユーザーが自身のセキュリティのためにブラウザの Cookie を削除した場合でも、Webサイトの運営者がブラウザに残った情報から Cookie を復元したり、ユーザーの行動をトラッキングする手法のこと。
- ユーザーの[プライバシー](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#privacy_and_tracking)と制御の原則に違反し、[データプライバシー規制](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie-related_regulations)に違反する可能性があり、それらを使用する Web サイトは法的責任を負う場合もある。

https://www.itmedia.co.jp/enterprise/articles/1108/21/news004.html

https://www.gizmodo.jp/2015/01/cookie_cookie.html

### Cookie の値の更新

- Cookie を更新するには、サーバーがレスポンスを返す際の `Set-Cookie` ヘッダーで、「既存の Cookie の名前」と「新しい値」を設定する。

```
Set-Cookie: id=new-value
```

#### Cookie の値の更新を行いたいケース

- ユーザーが環境設定を更新し、アプリケーションがクライアント側データに変更を反映させたい場合など。
    -  例1. ユーザーがテーマを「ライトモード」から「ダークモード」に変更した際に、その設定を Cookie に保存する。
    -  [Web Storage API](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API) を使用して、`localStorage` や `sessionStorage` に設定を保存することも可能。

### JavaScript 経由で Cookie を更新する

- **`HttpOnly` 属性が設定されていない場合** （かつ、サーバーから `Set-Cookie` ヘッダーで Cookie が設定されていた場合）は、JavaScript を使用して Cookie の値を読み書きすることができる。
    - **[`Document.cookie`](https://developer.mozilla.org/ja/docs/Web/API/Document/cookie) プロパティ** を使用して Cookie を設定、取得できる
    - **[Cookie Store API](https://developer.mozilla.org/ja/docs/Web/API/Cookie_Store_API)** を使用して非同期的に Cookie を管理できる

```js
console.log(document.cookie);
// logs "yummy_cookie=chocolate; tasty_cookie=strawberry"

document.cookie = "yummy_cookie=blueberry";

console.log(document.cookie);
// logs "tasty_cookie=strawberry; yummy_cookie=blueberry"
```

:::note warn
注:
- `HttpOnly` 属性が設定された Cookie は、JavaScript からアクセスできない。
- `SameSite` 属性が設定されている場合、クロスサイトリクエスト時の Cookie 送信が制限される。
- `fetch()` や `XMLHttpRequest` などの同一オリジンポリシーにより、クロスサイトリクエストでの Cookie の送信や更新が制限される。
:::

## セキュリティ

- Cookie に情報を格納した場合、デフォルトではすべての Cookie の値がユーザーに表示され、ユーザーは Cookie の値を変更することができてしまう。
- しかし、 Cookie が悪用されることは絶対に避けなければならない。
- 犯罪者がユーザーのセッションIDを盗み、犯罪者はそのセッションIDを使用して犯罪者自身の Cookie を設定し、別のユーザーとしてログインしているように見せかけ、その過程で別のユーザーの銀行や電子商取引のアカウントを乗っ取ることもできる。

### Cookie へのアクセスを制限する

- Cookie が安全に送信され、意図しない第三者やスクリプトからアクセスされないようにするには、**`Secure` 属性**と **`HttpOnly` 属性**の 2つの方法がある。

```
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
```

#### `Secure` 属性

- **HTTPS プロトコル上で暗号化されたリクエストでのみサーバーに送信される**。
- (localhost を除いて) 安全でない HTTP では決して送信されないため、[中間者攻撃](https://developer.mozilla.org/ja/docs/Glossary/MitM)の攻撃者が簡単にアクセスすることはできないようになる。
- URL に（`https:` ではなく）`http:` とついている安全でないサイトには、 `Secure` 属性を持つ Cookie を設定することができない。
- ただし、`Secure` 属性によって Cookie 内の機密情報へのアクセスをすべて防げるわけではない。
    - クライアントのハードディスクへのアクセス
    - HttpOnly 属性が設定されていない場合の JavaScript 経由でのアクセス

#### `HttpOnly` 属性

- **JavaScript 経由で Cookie にアクセスすることを禁止する**。
    - `Document.cookie` などを使用して Cookie を操作することができなくなり、サーバーの `Set-Cookie` を通じてのみアクセスすることができるようになる。
    - ユーザーのセッションを維持するための Cookie は`HttpOnly` 属性をつけるべきであり、JavaScript 経由でアクセスできることは全く安全ではない。
- これにより、[クロスサイトスクリプティング（XSS）](https://developer.mozilla.org/ja/docs/Web/Security/Types_of_attacks#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0_xss)攻撃の影響が軽減される。

:::note warn
注: 

Webアプリケーションでは、ユーザーの**機密情報**（例: パスワード、個人識別情報、支払い情報など）を Cookie に直接保存することは**セキュリティ上のリスク**があるため、避けるべき。
機密情報を扱うには、以下のような方法が推奨される。

#### 1. **セッションID** 
- セッションIDは、ユーザーごとに一意に生成されるランダムな文字列。
- これを Cookie に保存することで、実際の機密情報をクライアント側に保持せずに済む。
- サーバー側でセッションIDとユーザーの情報を対応付けるため、セッションIDが漏洩しても直接的な機密情報の漏洩を防ぐことができる。

```
Set-Cookie: session_id=abc123xyz; Path=/; HttpOnly; Secure; SameSite=Strict
```

#### 2. **JSON Web Token (JWT)** 
- JSON Web Token (JWT)は、ヘッダー、ペイロード、署名から構成され、1つのトークンとしてエンコードされたもの。
- サーバーはトークン内の情報を検証し、ユーザーの認証状態を確認することができる。

```
Set-Cookie: auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; Path=/; HttpOnly; Secure; SameSite=Strict
```
:::

### Cookie の送信先（スコープ）を定義する

-  **`Domain` 属性**と **`Path` 属性**は、「ブラウザが、どのサーバーのドメイン・パスに対して Cookie を送受信するか」を制御する。

#### `Domain` 属性

- サーバーは `Set-Cookie` ヘッダーの `Domain` 属性に、**自身のドメインまたはその親ドメインのみ** を設定できる。
- ブラウザは **`Domain` 属性に指定されたドメインとそのサブドメインにのみ**、Cookie を`Cookie` ヘッダーに含めてリクエストを送信することができる。
    - 例1: `mozilla.org` というドメインを持つサーバーが、 `Set-Cookie` ヘッダーに `Domain=mozilla.org` を指定してレスポンスを返した場合、ブラウザはそのドメイン `mozilla.org` とそのサブドメイン `developer.mozilla.org, etc` に対してのみ `Cookie` ヘッダーに Cookie を指定してリクエストを送信することができる。
    - 例2: `foo.example.com` というドメインを持つサーバーは、`Domain` 属性を `example.com` か `foo.example.com` に設定できるが、`bar.foo.example.com` や `elsewhere.co` に設定することはできない。
        ただし、Cookie は `bar.foo.example.com` などのサブドメインには送信される。

```
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Domain=mozilla.org
```

##### `Domain` 属性が指定されていない場合

- サーバーが `Set-Cookie` ヘッダーの Domain 属性を指定しなかった場合、ブラウザは、Cookie をそのサーバーのホスト（完全一致するドメイン `mozilla.org` ）にリクエストを送信する際にのみ使用する **Host-Only Cookie** として保存する。
    - **そのサブドメイン（ `developer.mozilla.org` ）に対してはリクエストを送信できなくなる** ため、セキュリティが向上する
- See also: [不正なドメイン - Set-Cookie - MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie#%E4%B8%8D%E6%AD%A3%E3%81%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3)
 
#### `Path` 属性

- サーバーは `Set-Cookie` ヘッダーの `Path` 属性を指定すると、ブラウザは `Path` 属性で指定されたURLパスの範囲に対してのみ、Cookie ヘッダーに Cookie を指定してリクエストを送信することができる。
- `%x2F` ("/") という文字はディレクトリの区切り文字とみなされるため、**サブディレクトリも一致する**。
    - たとえば、 `Path=/docs` を設定すると、次のリクエストパスが一致する。
        - `/docs`
        - `/docs/`
        - `/docs/Web/`
        - `/docs/Web/HTTP`
    - 次のリクエストパスは一致しない。
        - `/`
        - `/docsets`
        - `/fr/docs`

```
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Path=/docs
```

### クロスサイトリクエストフォージェリ（CSRF / Cross-Site Request Forger）対策

#### クロスサイトリクエスト（Cross-Site Request）

- ユーザーが、現在アクセスしているサイトとは異なる **意図しないサイトに対してリクエストを送信してしまうこと** 。
- 例1.
    1. ユーザーが SNS（例: `twitter.com` ）で共有されている「Amazon 商品ページ」へのリンクを見つける。
    1. そのリンクをクリックすると、ブラウザのアドレスバーが `twitter.com` から `amazon.co.jp` に切り替わり、Amazon の商品ページへ移動する。
- 例2.
    1. ユーザーがニュースサイト（例: `news.example.com` ）を閲覧している。
    1. 記事内に YouTube 動画が埋め込まれており、`<iframe>` タグや `<script>` タグを通じて youtube.com のリソースが読み込まれる。
    1. ブラウザは `news.example.com` から `youtube.com` に対して動画データやスクリプトの取得リクエストを自動的に送信する。

#### 対策方法

##### CSRFトークン ※ SSRの場合

- CSRFトークン
    - リクエストが正規の画面から送られたかどうかを検証する、リクエストの「正当性」を確認するための使い捨ての値。
    - サーバーがランダムな文字列（例：トークン）を発行し、フォームやリクエストに仕込ませる。
    - リクエスト時にそのトークンが送られてきたら「このリクエストは正しい画面から送られた」とみなす。
    - トークンがなかったり間違っていたら、リクエストを拒否する。
- Synchronizer Token Pattern
    - CSRFトークンを使って、攻撃者や悪意ある第三者サイトからリクエストを送る（CSRF攻撃）を防ぐパターン。

##### AuthorizationヘッダーとCORS設定 ※ SPA + APIの場合

- `Authorization` ヘッダーにBearerトークン（またはセッションID）を載せる
    - Bearerトークン
        - OAuth2の標準、OIDCにおける認証用のトークン。
        - 中身としてJWTが使われる場合が多い。
        - `Authorization: Bearer トークン`
- CORS（Cross-Origin Resource Sharing）
    - 他のドメインから送信されたリクエストを、サーバー側で受け入れるか拒否するかを制御する、サーバー側の設定。
    - 例えば、次のようにCORS設定を行うことで、APIサーバーで以下のことが達成できる。
        - 許可されたオリジン（例: https://your-spa.example.com ）から来たリクエストだけを受け入れる
        - 許可したリクエストヘッダー（Authorizationなど）だけを許可する
- 実際にリクエストが行われた時、以下のように動作する。
    1. ブラウザがプリフライトリクエスト（OPTIONS）を送信する
        ```
        OPTIONS /userinfo HTTP/1.1
        Origin: https://your-spa.example.com # 送信元
        Access-Control-Request-Method: GET # 本来送る予定のメソッド
        Access-Control-Request-Headers: Authorization, Content-Type # リクエストに使うカスタムヘッダ
        Host: api.example.com # 送信先
        ```
    1. APIサーバーがプリフライトリクエストに応答する
        ```
        HTTP/1.1 204 No Content
        Access-Control-Allow-Origin: https://your-spa.example.com
        Access-Control-Allow-Methods: GET, POST, PUT, DELETE
        Access-Control-Allow-Headers: Authorization, Content-Type
        Access-Control-Max-Age: 86400
        ```
    1. APIサーバーの設定が条件を満たす場合、ブラウザは本リクエスト（実際のAPIリクエスト）を送信する
        ```
        GET /userinfo HTTP/1.1
        Origin: https://your-spa.example.com
        Authorization: Bearer abcdefg12345
        Content-Type: application/json
        Host: api.example.com
        ```
    1. APIサーバーの設定が条件を満たさない場合、ブラウザコンソールにエラーが出る
        ```
        Access to fetch at 'https://api.example.com/userinfo' from origin 'https://your-spa.example.com' has been blocked by CORS policy: 
        Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
        ```


##### `SameSite` 属性

- サーバーは、ブラウザが **クロスサイトリクエストを行うときに Cookie を送信するかどうか** を制限することができる。
- `SameSite` 属性は、以下の3つの値を指定できる。
1. `Strict`
1. `Lax`
1. `None`

:::note warn
注意: 最近の主要ブラウザ（Chrome、Edge、Firefox）では、`SameSite` 属性が明示されていない Cookie に、 **`Lax` をデフォルト適用**する挙動になっている（2020年以降の仕様変更）。
ただし、古いブラウザや一部例外もあるため、**明示的に設定することが推奨**される。
:::

##### `SameSite=Strict` 

以下のようにサーバーのレスポンスヘッダーで設定したとき、

```
Set-Cookie: cart=110045_77895_53420; SameSite=Strict
```

- ブラウザは、同一サイトからリクエストを送信するときのみ、 Cookie を送信する。
- ログインセッションや支払い・カート情報など、常に同一サイト内でのみ利用される機密情報を送信するときに使用する。
- 例えば、ユーザーが銀行サイト ( `bank.example.com` ) へアクセスし、ログインフォームから認証を行うと、 `bank.example.com` のサーバーが `SameSite=Strict` を指定した `Set-Cookie` ヘッダーを返す。
これにより、ブラウザは同一サイト ( `bank.example.com` ) でのみ、セッションIDを持つ Cookie を含めてリクエストを送信するようになり、認証状態が維持される。
仮に、ユーザーが攻撃者によって用意された別ドメイン（ `malicious-site.com` ）上のリンクやフォームから `bank.example.com` にリクエストを送信してしまっても、 `SameSite=Strict` 設定のおかげでセッションID `session_id` は送られないため、CSRF 攻撃を防ぎやすくなる。

:::note warn
注意: 機密情報に使用される Cookie の有効期間も短くする必要がある。
:::

##### `SameSite=Lax` 

以下のようにサーバーのレスポンスヘッダーで設定したとき、

```
Set-Cookie: affiliate=e4rt45dw; SameSite=Lax
```

- ブラウザは、トップレベルナビゲーション（サイトのアドレスバーのURLが切り替わるようなユーザー操作）では Cookie を送信する。
- ブラウザは、サブリクエスト（ `fetch()` や `XMLHttpRequest` 、`<iframe>`, `<img>`, `<script>` などの埋め込み要素によるリクエスト）では Cookie を送信しない。
- 例えば、Amazonのようなパートナーサイトでは、ユーザーが第三者の運営するアフィリエイトサイトからリンクを踏んでAmazonに流入してきたことが追跡できるように、クロスサイトである第三者の運営するアフィリエイトサイトからリクエストが送信されるときも、Amazonのサーバーが返却した Cookie を送信する必要がある。そのため、Amazon のサーバーでは `Set-Cookie` で `SameSite=Lax` を設定する必要がある。

##### `SameSite=None` 

以下のようにサーバーのレスポンスヘッダーで設定したとき、

```
Set-Cookie: widget_session=7yjgj57e4n3d; SameSite=None; Secure; HttpOnly
```

- ブラウザは、すべてのリクエスト（同一サイト / 異なるサイト問わず）で Cookie を送信する。
- `SameSite=None` が設定されている場合は、安全のため、 `Secure` 属性も設定する必要がある。
- 例えば、広告サーバー（ `ads.example.net` ）では、ユーザーが第三者が運営するメディアサイト（例：`news.example.com` ）から広告となるスクリプトや画像を読み込んだことがわかるように、クロスサイトであるメディアサイトからリクエストが送信されるときも、広告サーバーが返却した Cookie を送信できる必要がある。そのため、広告サーバーでは `Set-Cookie` で `SameSite=None` を設定する必要がある。

## クッキーのプレフィックス

### サーバーは Cookie がどこで設定されたかを判別できない

- Cookie の仕組み上、サーバーは、リクエストに含まれる Cookie が実際にどのドメインで設定（= `Set-Cookie` ）されたかを確認することができない。
- 例えば、 **サブドメイン（ `foo.example.com` ）が上位ドメイン（ `exapmle.com` ）を `Domain` 属性に指定し、 Cookie を設定すると、 `exapmle.com` 配下のすべてのサブドメイン（ `foo.example.com` , `bar.example.com` , `baz.example.com` , etc）でその Cookie を利用することができてしまう**。
- 「**セッション固定攻撃**」のために悪用される可能性がある。
- 「**多層防御 (Defense in Depth)**」 として、Cookie 名に特定の接頭辞（プレフィックス）を付与することで「この Cookie は特定の条件を満たす Cookie である」ことをブラウザに宣言し、ブラウザ側で不正な Cookie 設定を防ぐ仕組みがある。

###  `__Host-` と `__Secure- `

- Cookie 名の先頭に `__Host-` または `__Secure-` を付けると、ブラウザは、それぞれ以下のような制約を満たさない限り、その `Cookie` をブラウザに設定しないようになる。

#### __Host-

Cookie 名にこの接頭辞がついている場合、レスポンスの `Set-Cookie` ヘッダーがブラウザで受信できるのは、以下の場合のみ。

- `Secure` 属性が付与されている
- リクエストが HTTPS 通信によって行われた
- `Domain` 属性を指定していない
- `Path` 属性が `/` になっている

これらをすべて満たす Cookie は、サブドメインや別パス経由で上書き・奪取されるリスクを低減することができる（= ドメインロック＋パスルート強制）。
例えば、サブドメインで `__Host-` プレフィックス付き Cookie を設定しようとしても、 `Domain` 属性を含んでいる場合などはブラウザ `Set-Cookie` ヘッダーを受信しない。


#### __Secure-

Cookie 名にこの接頭辞がついている場合、レスポンスの `Set-Cookie` ヘッダーがブラウザで受信できるのは、以下の場合のみ。

- `Secure` 属性が付与されている
- リクエストが HTTPS 通信によって行われた

`__Host-` よりも要件は緩やかで、 `Domain` や `Path` の指定は許容される。

:::note info
メモ: 
Webアプリケーション側では、プレフィックスを含む完全な Cookie 名をそのままチェックする必要がある（User Agent によって、リクエスト送信前にプレフィックスが取り除かれるといったことはない）。
:::

## プライバシーとトラッキング

### SameSite 属性の役割

- 先に書いたように、 `SameSite` 属性を使うと、クロスサイトリクエストの時に Cookie を送信するかどうかを制御できる。
- これは CSRF 攻撃の防止や、一部のトラッキングを抑止する上で役立つが、第三者ドメインによるクロスサイト追跡（サードパーティ Cookie）のすべてを防ぐわけではない。

### サードパーティ Cookie とは

- **現在アクセスしているサイトとは異なるサイト（第三者のドメイン）によってブラウザに設定される Cookie** のこと。
- 例えば、ユーザーが `news.example.com` を閲覧しており、そのページの中に、別ドメインの `<iframe>` タグや広告や分析サービス（例: `ads.provider.net` ）のスクリプトや画像などが埋め込まれている場合、ブラウザが `news.example.com` から `ads.provider.net` へリクエストを行うと、レスポンスに `Set-Cookie` ヘッダーが含まれる。こうして、「閲覧中のドメイン( `news.example.com` )とは異なるドメイン( `ads.provider.net` )」がブラウザに Cookie を設定するため、それがサードパーティ Cookie となる。

### クロスサイトトラッキングとブラウザによる規制

- サードパーティ Cookie の使い方次第では、ユーザーに望まれない形で行動プロファイリングが行われるリスクがある。
    - 例えば、「サイトA」で商品の情報を調べ、別の「サイトB」や「サイトC」に移動しても、同じ第三者ドメインが提供する広告ネットワークにより、「サイトA」の閲覧履歴を元に似た製品の広告が延々と表示される、ということがあり得る。
    - ユーザーの閲覧行動が多数のサイトにわたって関連付けられるため、プライバシー面で問題視されることがある。
- 多くのブラウザーメーカーは、こうした **クロスサイトトラッキング** がユーザーに好まれないことを把握しており、以下のようにサードパーティ Cookie をブロックする動きを強めている。
    - **デフォルトでブロック**
        - 一部のブラウザーでは、サードパーティ Cookie をデフォルト設定で完全にブロックする。
    - **段階的なブロック**
        - 一部のブラウザーでは、サードパーティ Cookie を段階的に廃止・制限する方向へ進んでいる。
    - **ユーザー設定や拡張機能によるブロック**
        - 一部のブラウザーでは、ユーザーが手動でサードパーティ Cookie をブロックしたり、トラッキング防止機能を有効化することができる。
- サードパーティ Cookie をブロックすると、以下のような影響が考えられるため、将来的にさらに厳しい制限が施されることを見据えて、サードパーティ Cookie に依存しない仕組みを検討する必要がある。
    - ソーシャルメディアウィジェットや埋め込みコンテンツが正しく動作しなくなる場合がある。
    - 広告や分析ツールが十分に機能しなくなる可能性がある。

## Cookie に関する規制

- Webサイトでの Cookie の使用に関する法規制として、以下のようなものがある。

### 主な法規制
- 以下の規制は、それぞれの管轄区域（**EU とカリフォルニア州**）に居住するユーザーがアクセスするウェブサイトに適用され、グローバルに影響を及ぼす。
- **The [General Data Privacy Regulation](https://gdpr.eu/) (GDPR) in the European Union**
- **The ePrivacy Directive in the EU**
- **The California Consumer Privacy Act(CCPA)**
    - 特に、カリフォルニア州の CCPA は、年間売上高が2,500万ドル以上の事業者に適用される。

### 規制の要件

- 上記の規制は、ユーザーのプライバシーを保護するために以下のような要件を設けている。
- **Cookie 使用の通知**
    - Webサイトは、Cookie の使用目的や種類をユーザーに対して明示的に通知する必要がある。
    - 通知は分かりやすく表示され、ユーザーが容易にアクセスできる場所（例：バナー、ポップアップ）に配置されることが求められる。
- **ユーザーによる Cookie の管理**
    - ユーザーが一部またはすべての Cookie をオプトアウト（拒否）できるようにする必要がある。
    - オプトアウトの方法は簡便で、ユーザーが容易に設定を変更できるインターフェースが提供されることが求められる。
- **サービスの利用性維持**
    - ユーザーが Cookie を拒否しても、Webサイトの主要な機能やサービスが利用可能であることを保証する必要がある。
    - 必須でない Cookie を拒否した場合でも、ユーザーエクスペリエンスが著しく損なわれないよう配慮が求められる。

### その他の地域の規制

- 地域によっては、上記以外にも Cookie の使用を管理する法規制が存在する。
    - **Lei Geral de Proteção de Dados（LGPD）※ ブラジル** 
    - **PIPEDA（Personal Information Protection and Electronic Documents Act）※ カナダ**
- これらの規制も、対象となる地域のユーザーに対して Cookie の使用を適切に管理することを義務付けており、Webサイト運営者は、対象地域の法規制を理解し、遵守する責任がある。

### 規制遵守のための支援ツール

- 法規制を遵守するための「Cookie 同意管理」ツールや「Cookie バナー」を提供するサービスが多数存在する。
- これらのツールを利用することで、以下のような対応が容易になり、法規制遵守の負担を軽減し、ユーザーのプライバシー保護を強化することができる。
    - **同意取得の自動化**
        - ユーザーからの同意を自動的に取得し、記録する機能。
    - **Cookie の分類と管理**
        - 必須 Cookie、パフォーマンス Cookie、広告 Cookie など、種類ごとに管理・制御する機能。
    - **ユーザー設定の保存**
        - ユーザーが選択した Cookie の設定を保存し、次回以降の訪問時に適用する機能。

## 参照
- [HTTP Cookie の使用 - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
- 関連するHTTPヘッダー: [`Set-Cookie`](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie) 、[`Cookie`](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cookie)
- 関連するJavaScript API: [`Document.cookie`](https://developer.mozilla.org/ja/docs/Web/API/Document/cookie) , [`Navigator.cookieEnabledStore`](https://developer.mozilla.org/ja/docs/Web/API/Navigator/cookieEnabled), [`Cookie Store API`](https://developer.mozilla.org/ja/docs/Web/API/Cookie_Store_API) 
- [サードパーティCookie](https://developer.mozilla.org/en-US/docs/Web/Privacy/Third-party_cookies)
- [Cookie仕様: RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265)
- [クッキー、GDPR、eプライバシー指令](https://gdpr.eu/cookies/)
- [【基礎から学ぶ】ELBのスティッキーセッションについてまとめてみた - サーバーワークスエンジニアブログ](https://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/)