HTTP Cookie

developers-jp.googleblog.com

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文章のドメインと同じドメインならオリジンやセイムサイト、別のドメインから取得していれば、クロスオリジンやクロスサイト、クロスドメインと言ったりします。

f:id:gkuga:20191207195639p:plain

ファーストパーティとサードパーティ

クロスオリジンへのアクセスの時もCookieが送られますが、そのCookieサードパーティと呼びます。 オリジンサーバへ送られるCookieはファーストパーティと呼びます。

f:id:gkuga:20191207200303p:plain

ユーザ、ユーザエージェント、ブラウザ

ブラウザはユーザエージェントの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.combar.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
    • リンクを踏んで移動するようなGETのHTTPリクエストにおいてはCookieが送られます。
  • None
    • これまでと同様にクロスオリジンでもCookieを送ります。
    • Secure属性も一緒につけないとエラーになります。

現状はSameSiteを指定しないとNoneと同じ挙動ですが、2月の変更からSameSite=Laxがデフォルトになります。

感想

  • ちゃんとCookieを使ってこなかった人生だなと思った。

参考