SameSite Cookie の使用

ChromeFirefoxEdge などでは、IETF の提案である Incrementally Better Cookies に従って、デフォルトの動作が変更されます。

  • SameSite 属性のない Cookie は SameSite=Lax として扱われます。つまり、デフォルトの動作では、Cookie をファーストパーティのコンテキストのみに制限します。
  • クロスサイトで使用する Cookie で、サードパーティのコンテキストに含めるには、SameSite=None; Secure を指定する必要があります

この機能は、Chrome 84 安定版以降のデフォルトの動作です。 サードパーティ Cookie の属性をまだ更新していない場合は、今後ブロックされないように更新してください。

クロスブラウザ サポート

MDN の Set-Cookie ページのブラウザの互換性のセクションをご覧ください。

クロスサイト Cookie またはサードパーティ Cookie のユースケース

サードパーティのコンテキストで Cookie を送信する必要がある一般的なユースケースやパターンは多数あります。これらのユースケースのいずれかを提供するか、依存する場合は、サービスが正しく機能し続けるように、お客様またはプロバイダのいずれかが Cookie を更新していることを確認してください。

<iframe> 内のコンテンツ

<iframe> に表示される別のサイトのコンテンツが、サードパーティのコンテキストである。標準的なユースケースは次のとおりです。

  • 他のサイトから共有された埋め込みコンテンツ(動画、地図、コードサンプル、ソーシャル投稿など)。
  • 支払い、カレンダー、予約、予約機能などの外部サービスのウィジェット。
  • <iframes> がわかりにくいソーシャル ボタンや不正防止サービスなどのウィジェット。

ここで Cookie は、特に、セッション ステータスの維持、全般設定の保存、統計情報の有効化、既存アカウント ユーザーのコンテンツのカスタマイズなどに使用されます。

埋め込みコンテンツの URL がページの URL と一致しないブラウザ ウィンドウの図。
埋め込みコンテンツが最上位のブラウジング コンテキストと同じサイトのものではない場合は、サードパーティのコンテンツです。

さらに、ウェブは本質的にコンポーズ可能なため、<iframes> を使用して、トップレベルまたはファーストパーティのコンテキストでも表示されるコンテンツを埋め込むことができます。そのサイトで使用されている Cookie は、そのサイトがフレーム内に表示された場合、サードパーティ Cookie と見なされます。簡単に埋め込めるようにしながら Cookie を機能させるサイトを作成している場合は、それらのサイトがクロスサイト用にマークされているか、Cookie なしで適切にフォールバックできるようにすることも必要になります。

サイトへの「安全ではない」リクエスト

ここでは「安全ではない」と少し心配に思われるかもしれませんが、状態の変更を意図したあらゆるリクエストを指します。主に POST リクエストであるウェブの場合。 SameSite=Lax としてマークされた Cookie は、安全なトップレベル ナビゲーション(別のサイトに移動するリンクのクリックなど)で送信されます。ただし、POST を介して別のサイトに送信する <form> には Cookie は含まれません。

ページ間で移動するリクエストの図。
受信リクエストで「安全な」メソッドが使用されていれば、Cookie が送信されます。

このパターンは、ユーザーをリモート サービスにリダイレクトして、戻る前になんらかのオペレーション(サードパーティの ID プロバイダへのリダイレクトなど)を実行するサイトに使用されます。ユーザーがサイトを離れる前に、単一の使用トークンを含む Cookie が設定されます。この Cookie は、クロスサイト リクエスト フォージェリ(CSRF)攻撃を軽減するために、返されるリクエストでこのトークンをチェックできることを想定しています。返されたリクエストが POST 経由で届いた場合は、Cookie を SameSite=None; Secure としてマークする必要があります。

リモート リソース

ページ上のリモート リソースは、<img> タグや <script> タグなどからリクエストとともに送信される Cookie に依存している場合があります。一般的なユースケースとしては、トラッキング ピクセルやコンテンツのカスタマイズなどがあります。

これは、fetch または XMLHttpRequest によって JavaScript から開始されたリクエストにも適用されます。fetch()credentials: 'include' オプションを指定して呼び出された場合は、それらのリクエストで Cookie が使用されることが想定されていることを示しています。XMLHttpRequest で、withCredentials プロパティtrue に設定されているインスタンスを探します。これは、これらのリクエストで Cookie が使用されることが想定されることを示しています。これらの Cookie をクロスサイト リクエストに含めるには、適切にマークする必要があります。

WebView 内のコンテンツ

プラットフォーム固有のアプリの WebView はブラウザを利用しているため、同じ制限や問題が適用されるかどうかをテストする必要があります。Android では、WebView に Chrome が搭載されている場合、Chrome 84 で新しいデフォルトが直ちに適用されません。ただし、これらは将来適用することを目的としているため、テストを行い、準備する必要があります。また、Android では、プラットフォーム固有のアプリで CookieManager API を使用して Cookie を直接設定できます。ヘッダーまたは JavaScript で設定される Cookie と同様に、クロスサイトで使用する場合は SameSite=None; Secure を含めることを検討してください。

今すぐ SameSite を実装する方法

ファーストパーティのコンテキストでのみ必要な Cookie については、必要に応じて SameSite=Lax または SameSite=Strict としてマークすることをおすすめします。何もせず、ブラウザがデフォルトを適用できるようにすることもできますが、ブラウザ間で動作に一貫性がなくなり、各 Cookie に対するコンソールの警告が表示される可能性があります。

Set-Cookie: first_party_var=value; SameSite=Lax

サードパーティのコンテキストで Cookie が必要な場合は、必ず SameSite=None; Secure としてマークしてください。両方の属性が必要です。Secure を指定せずに None のみを指定すると、Cookie は拒否されます。ただし、ブラウザの実装には相互に互換性のない違いがあるため、後述の互換性のないクライアントの処理で説明されている緩和策を講じる必要があります。

Set-Cookie: third_party_var=value; SameSite=None; Secure

互換性のないクライアントの処理

None を含むこれらの変更とデフォルトの更新動作はまだ比較的新しいため、これらの変更の処理方法に関してブラウザ間で不整合があります。現在判明している問題については、chromium.org の更新ページをご覧くださいが、すべてを網羅しているわけではありません。この方法は理想的ではありませんが、移行フェーズ中に使用できる回避策があります。ただし、原則として、互換性のないクライアントは特殊なケースとして扱います。新しいルールを実装しているブラウザには例外を作成しないでください。

1 つ目の方法は、新旧両方のスタイルの Cookie を設定することです。

Set-cookie: 3pcookie=value; SameSite=None; Secure
Set-cookie: 3pcookie-legacy=value; Secure

新しい動作を実装しているブラウザでは Cookie が SameSite の値に設定されますが、他のブラウザでは、Cookie が無視されるか、誤って設定されることがあります。ただし、これらのブラウザでは 3pcookie-legacy Cookie が設定されます。含まれる Cookie を処理する際、サイトはまず新しいスタイルの Cookie の有無を確認し、見つからなかった場合は従来の Cookie にフォールバックする必要があります。

以下の例は、Express フレームワークとその cookie-parser ミドルウェアを使用して、Node.js でこの処理を行う方法を示しています。

const express = require('express');
const cp = require('cookie-parser');
const app = express();
app.use(cp());

app.get('/set', (req, res) => {
  // Set the new style cookie
  res.cookie('3pcookie', 'value', { sameSite: 'none', secure: true });
  // And set the same value in the legacy cookie
  res.cookie('3pcookie-legacy', 'value', { secure: true });
  res.end();
});

app.get('/', (req, res) => {
  let cookieVal = null;

  if (req.cookies['3pcookie']) {
    // check the new style cookie first
    cookieVal = req.cookies['3pcookie'];
  } else if (req.cookies['3pcookie-legacy']) {
    // otherwise fall back to the legacy cookie
    cookieVal = req.cookies['3pcookie-legacy'];
  }

  res.end();
});

app.listen(process.env.PORT);

この方法の欠点は、すべてのブラウザに対応するよう冗長な Cookie を設定する必要があり、Cookie の設定時と読み取り時の両方で変更が必要になることです。ただし、このアプローチは、動作に関係なくすべてのブラウザに対応し、サードパーティ Cookie が以前と同様に機能し続けるようにする必要があります。

または、Set-Cookie ヘッダーの送信時に、ユーザー エージェント文字列を介してクライアントを検出することもできます。互換性のないクライアントのリストを参照して、プラットフォームに適したライブラリ(Node.js の ua-parser-js ライブラリなど)を利用してください。ユーザー エージェントの検出を処理するライブラリを探すことをおすすめします。このような正規表現を自分で作成するのは望ましくありません。

この方法のメリットは、Cookie の設定時に 1 つの変更を行うだけで済むことです。ただし、ここで必要な警告は、ユーザー エージェント スニッフィングは本質的に脆弱であり、影響を受けるすべてのユーザーを捕捉できるわけではないということです。

言語、ライブラリ、フレームワークでの SameSite=None のサポート

ほとんどの言語とライブラリは Cookie の SameSite 属性をサポートしていますが、SameSite=None の追加はまだ比較的新しいため、標準的な動作の一部への対処が必要になる場合があります。これらについては、GitHub の SameSite サンプル リポジトリに記載されています。

困ったときは

Cookie はいたるところに存在しており、設定と使用場所を完全に監査しているサイトはほとんどありません。特に、クロスサイト ユースケースが混在している場合には、その傾向が顕著です。問題に遭遇した場合、誰もがその問題に遭遇したのは初めてかもしれないので、遠慮なく連絡してください。