Cookieについてあまり考えたことがなかったのですが、2020年2月のChrome 80から段階的に改善をする変更が入るということで整理しました。
2019年12月6日現在の仕様はRFC 6265 - HTTP State Management Mechanismです。上記の変更はクロスオリジンの場合のクッキーのデフォルトの扱いが変更になります。記事の下に付けた参考情報を見て書いてますので、正確な情報が知りたい方はそちらを見てください。知らない人がみてわかるように書こうと思いましたが、わかりにくいところ、間違い、図をいれてほしいところがあればコメントなどで教えてください。
用語
オリジンとクロスオリジン
オリジンかクロスオリジンかは文脈により変わります。例えば、例えば今アクセスしているURIで、この記事が配信されていると思います。この記事を配信しているプログラムは役割としてオリジンサーバです。配信されるHTMLはオリジンリソースと呼びます。この記事が他の記事に埋め込まれて参照される時に配信しているサーバはクロスオリジンサーバで、JSONなどで渡されるリソースはクロスオリジンリソースと呼びます。
誤解を恐れずに言うと、あるURIのHTML文章には、いろんなURIの画像やCSS,JSなどが含まれています。またJSでどこかのAPIを叩いていたりします。そのとき、HTML文章のドメインと同じドメインならオリジンやセイムサイト、別のドメインから取得していれば、クロスオリジンやクロスサイト、クロスドメインと言ったりします。
ファーストパーティとサードパーティ
クロスオリジンへのアクセスの時もCookieが送られますが、そのCookieをサードパーティと呼びます。 オリジンサーバへ送られるCookieはファーストパーティと呼びます。
ユーザ、ユーザエージェント、ブラウザ
ブラウザはユーザエージェントの1つです。 ユーザはサーバに対してリクエストをする役割を表現したい時に使おうと思います。 ブラウザは具体的なユーザエージェントをイメージしたい時に使おうと思います。
Cookie
HTTPヘッダ名のCookieと混ざるので、ヘッダを指す時はCookieヘッダと書きます。 ヘッダ名ではないCookieそのものを指す時にCookieかHTTP Cookieと書きます。 僕はチョコチップクッキーが好きです。Cookieがお菓子のクッキーから来ているのかは不明です。
HTTP Cookie
HTTP Cookieによってセッションの概念を実現できます。 例えばログインの仕組みを実装できます。 ユーザのリクエストに対してサーバはSet-Cookieヘッダにデータを入れてHTTPレスポンスをします。 ユーザはそのサーバから受け取ったCookieとドメインを保存しておきます。 同じドメインにHTTPリクエストをするときはCookieヘッダに先程のCookieをつけて送ります。 ユーザを識別するIDをCookieに入れておけばどのユーザからのリクエストがわかり、ログイン状態を実現できるという感じです。
Cookieの作り方
サーバがHTTPレスポンスを返す時にSet-Cookieというヘッダー名でKey-Valueの形でヘッダーの値を入れます。 するとブラウザはCookieを保存し以降のCookieをつけたリクエストを送ってくれるようになります。
Set-Cookie: = Key1=Val1
ブラウザからのリクエストにはCookieというヘッダ名の値に先程のKey-Valueが入れられて送られます。
Cookie: = Key1=Val1
複数のデータを保存する時は同じヘッダ名で複数行書きます。
Set-Cookie: Key1=Val1 Set-Cookie: Key2=Val2 Set-Cookie: Key3=Val3
ブラウザからサーバへ複数のCookieを送る時は、セミコロンで区切って送られます。
Cookie: Key1=Val1; Key2=Val2; Key3=Val3
Keyに使えるのはUSASCIIから制御文字や区切り記号を除いた文字です。 ValueはUSASCIIから制御文字、スペース、ダブルクォート、コンマ、セミコロン、バックスペースを除いた文字が使えます。 Valueはダブルクォートで囲むことができるが、スペースやコンマを入れることはできません。 任意のデータを入れたい時はbase64などでエンコーディングしてください。
Cookieの属性
Cookieには下記の属性をつけることができます。 属性をつけることで、例えばCookieに有効期限をもたせたりできます。
- Expires
- Max-Age
- Domain
- Path
- Secure
- HttpOnly
ユーザエージェントはクッキーと一緒につけられた属性も保存します。 サーバにリクエストする時は有効なクッキーのみ送ります。
Expires
有効期限を表していて、期限は日時で指定します。 Netscapeの仕様が元になっていて、互換性を保つために残されていますが、実際に使う時はMax-Ageを使います。 (http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html)
Max-Age
有効期限を表していて、期限を秒数で表します。 Max-AgeはIETFによって標準化されたものです。 (https://www.ietf.org/rfc/rfc2109.txt)
Max-Ageですが、対応していないブラウザも前はありました。 今だとIE11のバージョンによっては対応していないものがあるようです。 ただ流石にそのようなことを想定してWebサービスを作ることはないかと思うので もうMax-Ageだけでよいと思います。
Domain
クッキーを送る時にどのドメインのサフィックスまでマッチすれば送るかを決めます。
例えば foo.example.com
というオリジンサーバがクッキーに example.com
というDomain属性を指定したとします。
すると、ブラウザは example.com
や bar.example.com
にもクッキーを送ります。
foo.example.com
オリジンサーバから Domain属性にexample.jp
のようにサフィックスにないものが送られてきたら破棄されます。
また、もちろんトップレベルドメインはマッチしたとみなされません。
Path
クッキーを送る時にどのパスのプレフィックスまでマッチすれば送るかを決めます。
例えば Path=/foo
を指定すれば、/
へのアクセスの時は送られないし、/foo
や/foo/bar
のようなパスへのアクセスの時はクッキーが送られます。
Secure
ユーザエージェントが定義するセキュアなコネクションの時のみCookieが保存されます。 ブラウザは主にはHTTPSをセキュアなコネクションとしています。
HttpOnly
CookieをHTTPリクエストのみで扱えるようにします。 例えばJavascriptからはCookieを触れません。
Cookieの削除
同じ名前、Domain属性、Path属性のCookieを受け取った場合は古いほうが破棄されます。 削除したいクッキーと同じクッキーをMax-Age属性(またはExpires属性)を無効な期限に設定して送ると削除できます。
SameSite
SameSite属性はHTTP Cookieの新しい属性です。実装自体は既に主要なブラウザでされています。
Cookieが不正に利用される状況としてクロスオリジンへのクッキーの送信が元になっていることがあります。それならば、クロスオリジンへのリクエストでクッキーを送信しなければ良いのですが、例えばAPIを別ドメインにしているとか、埋め込んだ動画を後でみるリストに入れたいとか、いろいろユースケースがあります。
なので、SameSite属性はユースケースに応じてクロスオリジンにCookieを送るか決めれる属性となっています。 属性値は3つありサーバ側で下記のように指定します。実際はライブラリやフレームワークを使って指定すると思いますがHTTPレスポンスのヘッダはこんな感じです。
Set-Cookie: Key=Val; SameSite=Strict
- Strict
- クロスオリジンでのCookieが全く送られなくなります。リンクを踏んで移動する時も送られません。
- Lax
- None
- これまでと同様にクロスオリジンでもCookieを送ります。
- Secure属性も一緒につけないとエラーになります。
現状はSameSiteを指定しないとNoneと同じ挙動ですが、2月の変更からSameSite=Laxがデフォルトになります。
感想
- ちゃんとCookieを使ってこなかった人生だなと思った。