User-Agent Client Hints API の移行

ユーザー エージェント文字列に依存しないサイトを新しい User-Agent Client Hints に移行するための戦略。

ユーザー エージェント文字列は、ブラウザの重要なパッシブ フィンガープリント サーフェスであるだけでなく、処理も困難です。ただし、ユーザー エージェント データの収集と処理にはさまざまな正当な理由があるため、より優れたソリューションへの道筋が必要です。User-Agent Client Hints は、ユーザー エージェント データの必要性を明示的に宣言するメソッドと、データを使いやすい形式で返すメソッドの両方を提供します。

この記事では、ユーザー エージェント データへのアクセス権を監査し、ユーザー エージェント文字列の使用状況を User-Agent Client Hints に移行する方法について説明します。

ユーザー エージェント データの収集と使用を監査する

どのような形式のデータ収集でもそうですが、データを収集する理由を常に理解しておく必要があります。アクションを実行するかどうかにかかわらず、最初のステップは、ユーザー エージェント データを使用する場所と理由を把握することです。

ユーザー エージェント データが使用されているかどうかや、使用されている場所がわからない場合は、navigator.userAgent を使用するためのフロントエンド コードと、User-Agent HTTP ヘッダーを使用するためのバックエンド コードを検索することを検討してください。また、navigator.platformnavigator.appVersion など、すでに非推奨になっている機能を使用しているかどうかについても、フロントエンド コードを確認する必要があります。

機能面から見ると、コード内のどこに記録または処理しているかを考えます。

  • ブラウザの名前またはバージョン
  • オペレーティング システムの名前またはバージョン
  • デバイスのメーカーとモデル
  • CPU タイプ、アーキテクチャ、ビット数(64 ビットなど)

サードパーティのライブラリまたはサービスを使用してユーザー エージェントを処理している可能性もあります。この場合は、User-Agent Client Hints をサポートするように更新されているかどうかを確認します。

基本的なユーザー エージェント データのみを使用しているか。

User-Agent Client Hints のデフォルトのセットには次のものがあります。

  • Sec-CH-UA: ブラウザ名とメジャー/重要なバージョン
  • Sec-CH-UA-Mobile: モバイル デバイスを示すブール値
  • Sec-CH-UA-Platform: オペレーティング システム名
    • これは仕様で更新されており、まもなく Chrome とその他の Chromium ベースのブラウザに反映される予定です。

提案されるユーザー エージェント文字列の縮小版でも、下位互換性のある方法でこの基本情報が保持されます。たとえば、Chrome/90.0.4430.85 ではなく、文字列に Chrome/90.0.0.0 を含めます。

ユーザー エージェント文字列でブラウザ名、メジャー バージョン、オペレーティング システムのみを確認する場合は、サポート終了の警告が表示されることがありますが、コードは引き続き機能します。

User-Agent Client Hints に移行することは可能ですが、以前のコードやリソースの制約により移行できない場合もあります。この下位互換性のある方法でユーザー エージェント文字列の情報を削減することは、既存のコードが受け取る詳細情報は少なくても、基本的な機能は維持することを目的としています。

戦略: オンデマンドのクライアントサイド JavaScript API

現在 navigator.userAgent を使用している場合は、ユーザー エージェント文字列の解析にフォールバックする前に、navigator.userAgentData を優先するよう移行する必要があります。

if (navigator.userAgentData) {
  // use new hints
} else {
  // fall back to user-agent string parsing
}

モバイルまたはパソコンを確認する場合は、ブール値 mobile を使用します。

const isMobile = navigator.userAgentData.mobile;

userAgentData.brands は、brand プロパティと version プロパティを持つオブジェクトの配列です。ブラウザはこれらのブランドとの互換性をリストできます。配列として直接アクセスすることも、some() 呼び出しを使用して特定のエントリが存在するかどうかを確認することもできます。

function isCompatible(item) {
  // In real life you most likely have more complex rules here
  return ['Chromium', 'Google Chrome', 'NewBrowser'].includes(item.brand);
}
if (navigator.userAgentData.brands.some(isCompatible)) {
  // browser reports as compatible
}

詳細で高エントロピーのユーザー エージェント値が必要な場合は、その値を指定し、返される Promise で結果を確認する必要があります。

navigator.userAgentData.getHighEntropyValues(['model'])
  .then(ua => {
    // requested hints available as attributes
    const model = ua.model
  });

サーバーサイドの処理からクライアントサイドの処理に移行する場合にも、この戦略を使用することをおすすめします。JavaScript API は HTTP リクエスト ヘッダーへのアクセスを必要としないため、ユーザー エージェントの値をいつでもリクエストできます。

戦略: Static server-side ヘッダー

サーバーで User-Agent リクエスト ヘッダーを使用していて、そのデータのニーズがサイト全体で比較的一貫している場合は、必要なクライアント ヒントをレスポンスの静的セットとして指定できます。通常は 1 つのロケーションで構成する必要があるため、これは比較的シンプルなアプローチです。たとえば、ウェブサーバーの設定にすでにヘッダーが追加されている場合、ホスティング構成、サイトに使用するフレームワークやプラットフォームのトップレベル構成などに、すでに存在する可能性があります。

ユーザー エージェント データに基づいて提供されるレスポンスを変換またはカスタマイズする場合は、この戦略を検討してください。

ブラウザやその他のクライアントでは、提供するデフォルトのヒントが異なる場合があります。そのため、通常はデフォルトで提供されている場合でも、必要なものをすべて指定することをおすすめします。

たとえば、Chrome の現在のデフォルトは次のように表されます。

⬇️ レスポンス ヘッダー

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

レスポンスでデバイスモデルも受け取る場合は、次のように送信します。

⬇️ レスポンス ヘッダー

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA

これをサーバーサイドで処理する場合は、まず目的の Sec-CH-UA ヘッダーが送信されているかどうかを確認し、送信されていない場合は User-Agent ヘッダーの解析にフォールバックする必要があります。

戦略: ヒントをクロスオリジン リクエストに委任する

リクエストで User-Agent Client Hints を送信する必要があるクロスオリジン サブリソースまたはクロスサイト サブリソースをリクエストする場合は、権限ポリシーを使用して目的のヒントを明示的に指定する必要があります。

たとえば、https://blog.sitehttps://cdn.site でリソースをホストし、特定のデバイス向けに最適化されたリソースを返すことができるとします。https://blog.siteSec-CH-UA-Model ヒントを要求できますが、Permissions-Policy ヘッダーを使用して https://cdn.site に明示的に委任する必要があります。ポリシー制御のヒントのリストは、Clients Hints Infrastructure のドラフトから入手できます。

⬇️ ヒントを委任している blog.site からのレスポンス

Accept-CH: Sec-CH-UA-Model
Permissions-Policy: ch-ua-model=(self "https://cdn.site")

⚈️ cdn.site のサブリソースへのリクエストに委任されたヒントを含める

Sec-CH-UA-Model: "Pixel 5"

ch-ua 範囲からだけでなく、複数のオリジンに対して複数のヒントを指定できます。

⬇️ 複数のヒントを複数のオリジンに委任する blog.site からのレスポンス

Accept-CH: Sec-CH-UA-Model, DPR
Permissions-Policy: ch-ua-model=(self "https://cdn.site"),
                    ch-dpr=(self "https://cdn.site" "https://img.site")

戦略: iframe にヒントを委任する

クロスオリジンの iframe はクロスオリジン リソースと同じように動作しますが、委任するヒントは allow 属性で指定します。

⬇️ blog.site からの回答

Accept-CH: Sec-CH-UA-Model

blog.site の ↪️ HTML

<iframe src="https://widget.site" allow="ch-ua-model"></iframe>

ꛭ️ widget.site へのリクエスト

Sec-CH-UA-Model: "Pixel 5"

iframe の allow 属性は、widget.site が自身に送信する Accept-CH ヘッダーをオーバーライドするため、iframe のサイトが必要とするものをすべて指定してください。

戦略: 動的なサーバーサイドのヒント

ユーザー ジャーニーの一部で、サイトの他の部分よりも多くのヒントが必要な場合には、サイト全体で静的にヒントをリクエストするのではなく、オンデマンドでヒントをリクエストすることもできます。管理はより複雑ですが、すでにルートごとに異なるヘッダーを設定していれば、それが可能になる場合があります。

ここで重要なのは、Accept-CH ヘッダーの各インスタンスが既存のセットを実質的に上書きすることです。そのため、ヘッダーを動的に設定する場合は、各ページで必要なすべてのヒントをリクエストする必要があります。

たとえば、サイトにユーザーのオペレーティング システムに合ったアイコンやコントロールを提供するセクションを 1 つ配置できます。この場合、さらに Sec-CH-UA-Platform-Version を pull して適切なサブリソースを提供することをおすすめします。

⬇️ /blog のレスポンス ヘッダー

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

⬇️ /app のレスポンス ヘッダー

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA

戦略: 最初のリクエストでサーバーサイドのヒントが必要

最初のリクエストでデフォルトのヒントセットを超えることが必要になる場合もありますが、まれなケースであるため、理由を必ず確認してください。

最初のリクエストは、実際にはそのブラウジング セッションで送信された、オリジンに対する最初のトップレベル リクエストを意味します。デフォルトのヒントセットには、ブラウザ名とメジャー バージョン、プラットフォーム、モバイル インジケーターが含まれます。ここで考えるべき問題は 最初のページ読み込み時に拡張データが必要かということです

最初のリクエストに関するその他のヒントについては、次の 2 つの方法があります。まず、Critical-CH ヘッダーを使用できます。これは Accept-CH と同じ形式を取りますが、最初のリクエストが重要なヒントなしで送信された場合は、すぐにリクエストを再試行する必要があることをブラウザに伝えます。

😝?️ 最初のリクエスト

[With default headers]

⬇️ レスポンス ヘッダー

Accept-CH: Sec-CH-UA-Model
Critical-CH: Sec-CH-UA-Model

🔃? ブラウザが追加のヘッダーを使用して最初のリクエストを再試行する

[With default headers + …]
Sec-CH-UA-Model: Pixel 5

最初のリクエストで再試行のオーバーヘッドが発生しますが、実装コストは比較的低くなります。追加のヘッダーを送信すると 残りの処理はブラウザが行います

最初のページの読み込み時に追加のヒントが必要な場合に備えて、Client Hints の信頼性に関する提案では、接続レベルの設定でヒントを指定するルートをレイアウトしています。これにより、TLS 1.3 の Application-Layer Protocol Settings(ALPS)拡張機能を利用して、HTTP/2 接続と HTTP/3 接続でヒントの早期受け渡しが可能になります。これはまだ初期段階ですが、独自の TLS 設定と接続設定を積極的に管理している場合は、改善に貢献する絶好のタイミングです。

戦略: レガシー サポート

サイト内に navigator.userAgent に依存する古いコードやサードパーティのコード(ユーザー エージェント文字列の一部など)が削減されている場合があります。長期的には、同等の navigator.userAgentData 呼び出しへの移行を計画する必要がありますが、暫定的な解決策があります。

UA-CH レトロフィルは、リクエストされた navigator.userAgentData 値から構築された新しい文字列で navigator.userAgent を上書きできる小さなライブラリです。

たとえば、次のコードは、「モデル」ヒントを追加で含むユーザー エージェント文字列を生成します。

import { overrideUserAgentUsingClientHints } from './uach-retrofill.js';
overrideUserAgentUsingClientHints(['model'])
  .then(() => { console.log(navigator.userAgent); });

結果の文字列には Pixel 5 モデルが表示されますが、uaFullVersion ヒントがリクエストされなかったため、短縮された 92.0.0.0 が表示されます。

Mozilla/5.0 (Linux; Android 10.0; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.0.0 Mobile Safari/537.36

その他のサポート

これらの戦略でお客様のユースケースがカバーされない場合は、privacy-sandbox-dev-support リポジトリに関するディスカッションを開始してください。お客様の問題を一緒に調査いたします。

写真撮影: Ricardo Rocha、出典: Unsplash