User-Agent Client Hints API の移行

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

ユーザー エージェント文字列は、ブラウザにおいて重要なパッシブ フィンガープリント サーフェスとなるため、処理が困難です。ただし、ユーザー エージェント データの収集と処理にはさまざまな正当な理由があるため、必要なのは、より優れたソリューションへの道筋です。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.brandsbrand プロパティと 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 リクエスト ヘッダーを使用していて、そのデータのニーズがサイト全体で比較的一貫している場合は、レスポンスで目的のクライアント ヒントを静的セットとして指定できます。通常は 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 インフラストラクチャのドラフトにあります。

⬇️ ヒントを委任した 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 retrofill は、リクエストされた navigator.userAgentData 値から構築された新しい文字列で navigator.userAgent を上書きできる小さなライブラリです。

たとえば、このコードは「model」ヒントを付加した user-agent 文字列を生成します。

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