ソフトウェアエンジニアの太田です。今日はクリスマスシーズンにふさわしくクッキー(とプライバシー)の話をしたいと思います。

Google Developers のブログでアナウンスされている通り、2020年2月にリリースされるChrome 80 ではクロスサイトコンテキストにおけるクッキーの動作が変わります。弊社の広告サーバーでもその対応を行いましたが、各ブラウザベンダーのプライバシーに対する対策の違いについてや、実際に開発する上での落とし穴など多くの学びがあったため共有します。ただし広告事業者としてシステムを開発している私が、プライバシーと広告について中立的に語ることはやはり難しいです。事実を並べるように務めましたが、私の考えにはバイアスがかかっているかもしれないことをご留意ください。

背景トラッキングと Cookie

ユーザーが直接訪れているウェブサイト以外に送られるクッキーをサードパーティークッキーと呼びます。さまざまなメディアに配信する広告配信システムにとって、サードパーティークッキーは不可欠なものです。例えばページを訪れたユーザーを識別したり、広告が表示されたことやクリックされたことを確認したり、広告を表示した結果どれだけのユーザーが広告主のサイトで会員登録や商品の購入など行動をとったか計測する(コンバージョントラッキング)など、さまざまな目的で使用されています。ユーザーのプライバシーを守った上で正確なデータを広告主に提供できるかが鍵となっています。

特にプライバシーの観点から大きな課題があるのは「行動ターゲティング」です。例えば「ニュースサイトを見ているとき、先週購入を検討していたモデルの自転車の広告が出てくる」といったように、まるであなたの行動をすべて見ているような広告に出会ったことがある人は多いのではないでしょうか。そのような広告の多くは、さまざまなウェブサイト上の行動履歴を収集しユーザーの興味関心にあった広告を出す行動ターゲティングという手法を使っています。行動ターゲティングではユーザーがどのサイトに訪問したのかトラッキングするために、クッキーを発行して各ユーザーを識別することが一般的です。

このようにプライバシーの課題を抱えるサードパーティークッキーに対してブラウザベンダーはそれぞれ対策を行ってきました。Apple は WebKit Tracking Prevention Policy においてクロスサイトトラッキングや意図しないトラッキングを防いでいくことを宣言し、その実装である Intelligent Tracking Prevention (ITP) を2017から開発・改善し続けてきました。また Mozilla の開発する Firefox や Microsoft の開発する Edge もトラッキング用のサードパーティークッキーをブロックする方法を提供しています。

それに対し Google の開発する Chrome は異なるアプローチをとっており、ユーザーの意図しないトラッキングを防ぐためフィンガープリンティング対策を行っていくことを宣言したり、より良いプライバシーを提供するようなコンバージョントラッキング用のAPIを提案したりと様々な改善を提案しています。その中の一つがSameSite 属性に関する変更です。端的にいうとサードパーティークッキーは明示的にサードパーティークッキーであることを宣言することが必要になりました。これによってそれぞれのクッキーの使用目的が明確になり、ユーザーはトラッキングをブロックすることが容易になります。またファーストパーティークッキーが意図せずにクロスサイトコンテキストで利用されることをデフォルトで防げるようになり、セキュリティを高めることができます。Firefox にも同じ変更が実装されており、近い将来有効化される可能性があります。

SameSite 属性の変更点

実際に何が起きるのでしょうか?Incrementally Better Cookies では以下の2つの変更が提案されています。

  1. Treat the lack of an explicit “SameSite” attribute as “SameSite=Lax”. That is, the “Set-Cookie” value “key=value” will produce a cookie equivalent to “key=value; SameSite=Lax”.
  2. Require the “Secure” attribute to be set for any cookie which asserts “SameSite=None” (similar conceptually to the behavior for the “__Secure-” prefix). That is, the “Set-Cookie” value “key=value; SameSite=None; Secure” will be accepted, while “key=value; SameSite=None” will be rejected.

まとめると、クロスサイトコンテキストでクッキーが送られるようにするためには、

  • SameSite=Note; Secure の2つの属性を設定し
  • セキュアな通信を行う

必要があります。SameSite はどのコンテキストでクッキーを送るか指定する属性で、None はクロスサイトコンテキストを含むすべてのコンテキストで送られることを示します。 Secure 属性をつけることでクッキーがセキュアな通信でのみ送られるようになるので、クロスサイトコンテキストのクッキーを受け付けるドメインは必ずセキュアな通信を行う必要があります。

対応

さて取るべき対応がわかり、私も最初はなんだ簡単じゃないかと思ったのですが、実はいくつか落とし穴がありました。

落とし穴1: SameSite=None に対応していないブラウザ

SameSite=None は比較的新しい仕様であるため、対応していないブラウザが存在しており、Chromium Project が対応していないブラウザおよび判別方法についてまとめています。

  1. 古いスペックに準拠しているため、Chrome 51 から Chrome 66 は SameSite=None のついたクッキーを弾く
  2. WebKit の バグのため、iOS 12 と MacOS 10.14 の Safari は、SameSite=None を SameSite=Strict として扱う

とくに日本においては 25% 程度の iOS で iOS 12 が使われているため、無視できない影響があります。

対策としては2つの方針が提案されています。

1. SameSite=None に対応していないブラウザを検知して、SameSite=None をつけるか切り替える

Chromium でも SameSite=None に対応していないブラウザを検知するサンプルコードが書かれていますが、かなり複雑な上に完璧である保証はありません。さらに User-Agent を自分でマッチするのではなく何らかのライブラリを使うことを推奨されています。

落とし穴:ユーザーエージェントのパース

我々の Java で書かれたサーバーで使うことを検討したのですが、目的にマッチするライブラリを見つけるのが困難でした。

  • nielsbasjes/yauaa
    • Yet Another UserAgent Analyzer. 独自の UserAgent ルールを持っている。レンダリングエンジンとそのバージョンを検知してくれるため、調べたライブラリの中で今回の目的に合う唯一のライブラリ。ただしシングルスレッド前提、メモリ使用量が多い、初期化が遅い等の課題はある。iOS の Chrome のレンダリングエンジンを Blink と宣言するおちゃめな面もあるが、Issue を立てたらすぐに直ったので開発は活発な様子。
  • blueconic/browscap-java
    • Browser Capabilities Project というコミュニティーに寄って管理された User Agent のデータを使ったパーサー。Blink や WebKit のバージョンを判別してくれないため、Chromium ベースのブラウザが SameSite=Strict を弾くかの判断ができず使用を断念。
  • woothee/woothee-java
  • ua-parser/uap-java
    • どちらもレンダリングエンジンを判別してくれないしてくれないため、 Chromium ベースかどうかの判断ができない。

最終的に yauaa を使うことにしましたが、やはり UserAgent をパースするのは複雑なため、次の方法がおすすめです。

2. SameSite=None を設定したクッキーと設定していないクッキーを両方書き込む

たとえば

と同じ値を2つの方式で書き込むことで、いずれかが必ず送られてくるようになります。これによりブラウザが SameSite=None に対応しているか、SameSite のデフォルトの値が Lax になっているか気にせずに必ず値を取ることができます。

落とし穴2: SameSite に対応していないフレームワーク

我々の広告配信システムは Netty という Web サーバーを使用する Spring WebFlux というフレームワークを用いて開発されています。しかし使っているバージョンの Spring WebFlux, Netty ではどちらもクッキーに SameSite 属性をつけることに対応していませんでした。幸い v5.1.10 では Netty のクッキーを使わず独自で Header を書くことで SameSite 属性の書き込みに対応しているので使っているフレームワークのバージョンを最新にすることで対応することができます。

より詳しく知るには

今回の記事では具体的な対応部分にフォーカスしたため、特に背景については大まかにしか触れられませんでしたが、より詳しい資料へのリンクをまとめています。

フライウィールではユーザーのプライバシーを守りつつ、データを人々のエネルギーにするために貢献したいエンジニアを募集中です。