Photo by Mohammad Rahmani on Unsplash
Chromeはバージョン84からCookieのSameSite属性のデフォルトをLaxにしました。Third-party cookiesを使用するサービスでSameSiteを設定していない場合は影響を受ける可能性があります。
概要
CookieはWebサービスにおいて状態を保存するための仕組みで、ログイン状態の維持、ショッピングカート、広告トラッキングなどによく使用されます。しかし、Cookieの広範な使用に伴い、プライバシーやセキュリティの懸念も生じており、それらの問題を解決するためにSameSiteが登場しました。
First-Party and Third-Party
Cookieのソース(Set-Cookie)に基づいて、各Cookieには固有のドメインがあります。ユーザーのブラウザの現在のURLを見て、Cookieのドメインが現在のURLと一致する場合はFirst-Party、そうでない場合はThird-Partyとなります。
Third-party
例えば、a.comを閲覧しているときにthird-party.comへリクエストを送信し、third-party.comからCookieを受け取ったとします。ブラウザはリクエスト時に同じドメインのCookieを自動的に含めるため、その後b.comのような他のサイトを閲覧した際にthird-party.comへリクエストが送信されると、サーバーはCookieを受け取ります。これら2つのサイトにとって、third-party.comのCookieはThird-partyとなります。
First-party
もしthird-party.comドメインに一致するWebサイトを閲覧した場合もCookieが送信されます。この場合、このCookieはFirst-partyと呼ばれます。
Same-Origin and Same-Site
先ほどの例ではドメインが一致するかどうかでCookieの種類を判定していましたが、より正確にはSiteが同じかどうかで判定すべきです。これはよく見かけるSame-originと関係があるのでしょうか?

Origin
OriginはScheme、Host、Portで構成されます。判定方法は非常にシンプルで、2つのURLのScheme、Host、Portがすべて同じであればSame-origin、そうでなければCross-originです。
Site
Same-Siteの判定にはEffective top-level domains(eTLDs)が関わってきます。すべてのeTLDsはPublic Suffix Listで定義されており、SiteはeTLDにプレフィックスを加えたもので構成されます。
例えば:
github.ioはPublic Suffix Listに存在します。これにプレフィックス(例:a.github.io)を加えるとSiteになります。したがって、a.github.ioとb.github.ioは2つの異なるSite(Cross-site)です。
example.comはPublic Suffix Listに存在しませんが、.comは存在するため、example.comはSiteであり、a.example.comとb.example.comは同じSite(Same-site)となります。
SiteにはPortが含まれないため、Portが異なっていてもSame-siteになることに注意してください。
Why SameSite?
「リクエストには必ずそのドメインのCookieを含める」という仕組みは、同時にセキュリティやその他の問題をもたらしました。その中で最も重要なのがCross-site request forgery(CSRF)です。
CSRF
ユーザーがexample.comにログインしてCookieを取得したと仮定します。ユーザーが悪意のあるサイトevil.comを閲覧すると、そのサイト内のJavaScriptがexample.com/pay?amount=1000に対してPOSTリクエストを送信する可能性があります。ブラウザは自動的にexample.comのCookieを含めるため、ユーザーは知らないうちに1000ドルを支払ってしまうことになります。サーバーはこのリクエストがどこから来たのかを判断できません。
制限
Cookie自体にはFirst-party環境でのみ送信するように設定する機能がないため、リクエストはどのような環境でもCookieを含んでしまいます。サーバーはリクエストの送信元を識別できず、通常通り応答するしかありません。同時に、クライアントは無駄なCookieを送信して帯域を浪費することになります。
解決策
SameSite属性があれば、異なる環境でのCookieの送信条件を個別に設定できます。
SameSite
SameSite属性には3つの値があります。StrictまたはLaxに設定すると、Same-SiteリクエストでのみCookieを含めるように制限できます。空欄のままにした場合の動作はブラウザによって異なりますが、Chromeの場合はデフォルトでLaxです。
Strict
First-party環境でのみCookieを含めます。しかし、これには問題があります。例えば、ユーザーがexample.comでFacebookの投稿リンク(fb.comと仮定)を見たとします。ユーザーがfb.comにログインしてCookieを持っていたとしても、リンクをクリックした後、2つのサイトはCross-siteであるためCookieは送信されず、ログインページが表示されるだけになります。
そのため、Strictは投稿の削除や支払いなどの操作に適しています。
Lax
Strictの厳しすぎる制限を解決するために、Laxは以下の場合、Cross-siteであってもCookieを送信します:
- アドレスバーへのURL入力
- リンク
<a href="...">のクリック - フォーム
<form method="GET">の送信 - プリレンダリング
<link rel="prerender" href="...">
これらの状況には2つの共通点があります。すべてGETリクエストであり、トップレベルナビゲーション(Top-level Navigation)をトリガーすることです。これにより、Strictのように毎回再ログインが必要になる問題を回避しつつ、他のWebサイトを閲覧しているときに知らないうちにCookieを送信してしまうことも防げます。
Lax + POST
しかし、既存の一部のログインフローを壊さないように、Chromeは現在SameSite=Laxの制限を少し緩和し、開発者に猶予を与えています。
Cookieが設定されてから2分以内であれば、リクエストメソッドに関係なく、トップレベルのページ遷移をトリガーする限りCookieが送信されます。つまり、フォーム送信<form method="POST">などでブラウザがページを移動する場合です。
詳細はLax + POSTに関するスレッドをご覧ください。
None
Third-party cookieを送信したい場合は、SameSite=None; Secureを設定する必要があります。そうです、これからはテスト環境でThird-party cookieを送信したい場合は、https://localhostを準備してください。
また、XHR/FetchでCross-Origin Requestを送信する場合、Cookieを含め、レスポンスヘッダーのSet-Cookieを有効にするには、別途withCredentials: trueを設定する必要があります。そしてサーバー側はレスポンスヘッダーにAccess-Control-Allow-Credentials: trueを設定しなければ、JavaScriptはレスポンスの内容にアクセスできません。
非対応ブラウザ
すべてのブラウザが最新のSameSiteルールに対応しているわけではないため、サーバー側でいくつかの一時的な回避策(Workaround)を追加して、複数のブラウザをサポートすることができます:
両方のCookieを設定する
この方法はほぼすべてのブラウザの問題を解決できますが、欠点はCookieが2つになることです:
Set-cookie: name=value; SameSite=None; Secure
Set-cookie: name-legacy=value; Secure
サーバー側のコード:
if (req.cookies['name']) {
// 新しいものがあれば新しいものを使う
cookieVal = req.cookies['name'];
} else if (req.cookies['name-legacy']) {
// なければ古いものを使う
cookieVal = req.cookies['name-legacy'];
}
User Agent
リクエストのUser agentからブラウザを判断してSet-Cookieの内容を決定します。この方法はCookieを設定するコードを修正するだけで済み、パース部分を変更する必要はありませんが、この判定方法は変数が多く、誤ったCookieを設定してしまう可能性が高くなります。
振り返り
- SameSite属性が設定されていないCookieは
SameSite=Laxとなり、Cross-site環境では送信できません。 - Cross-siteでCookieを送信したい場合は、
SameSite=None; Secureを設定する必要があります。 - SameSite sandboxを使用して、現在使用しているブラウザが最新のSameSiteルールに準拠しているかどうかをテストできます。