User-Agent Client Hints API の移行

ユーザー エージェント文字列に依存するサイトを新しいユーザー エージェント クライアントのヒントに移行するための戦略。

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

この記事では、ユーザー エージェント データへのアクセスを監査し、ユーザー エージェント文字列の使用を 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 リクエスト ヘッダーへのアクセスが不要であるため、ユーザー エージェントの値はいつでもリクエストできます。

戦略: 静的サーバーサイド ヘッダー

サーバーで User-Agent リクエスト ヘッダーを使用していて、そのデータのニーズがサイト全体で比較的一貫している場合は、必要な Client Hints をレスポンスで静的セットとして指定できます。通常は 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 に明示的に委任する必要があります。ポリシーで制御されるヒントのリストについては、Client Hints インフラストラクチャのドラフトをご覧ください。

⬇️ ヒントを委任する 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 を追加で取得して、適切なサブリソースを提供する必要がある場合があります。

⬇️ /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

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

最初のページ読み込みで追加のヒントが本当に必要な場合は、クライアント ヒントの信頼性に関する提案で、接続レベルの設定でヒントを指定するルートを定義しています。これは、TLS 1.3 の アプリケーション レイヤ プロトコル設定(ALPS)拡張機能を使用して、HTTP/2 接続と HTTP/3 接続でヒントを早期に渡します。まだ初期段階ですが、独自の TLS と接続設定を積極的に管理している場合は、この機会にぜひご協力ください。

戦略: 以前のサポート

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

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

たとえば、次のコードでは、「model」ヒントが追加されたユーザー エージェント文字列が生成されます。

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