複数のドメインでスムーズなコンテキスト認証を提供するために、組織はログインページを iframe 内に埋め込むことがよくあります。ただし、サードパーティ フレーム内で認証コンテキストを読み込むと、クリックジャッキング(UI の再調整)や不正な認証情報の作成などの重大な脅威にユーザーがさらされる可能性があります。こうしたリスクを軽減するため、ブラウザではデフォルトでクロスオリジン iframe での WebAuthn が無効になっています。この制限を安全に解除するには、アクティブな多層防御プロトコルが必要です。
脅威モデルを特定する
サブフレーム内でパスキー(WebAuthn)を有効にする前に、防御対象となる不正使用のシナリオを理解してください。
- 非表示の iframe インジェクションを使用したトラッキング: 攻撃者は、信頼できるサイトの広告やウィジェットを使用して、自身のドメインから WebAuthn プロンプトをトリガーし、ユーザーにコンテキストを表示せずにパスキーの承認を促します。これにより、ユーザーの ID が攻撃者が管理するアカウントにリンクされ、データが収集されます。
- 視覚的なオーバーレイとクリックジャッキング(UI の再調整): 悪意のある親ページが標準の CSS を使用して認証 iframe を非表示にし、偽の UI 要素を重ねて、認証フローをトリガーするクリックを盗みます。ユーザーが誤ってプロンプトを完了すると、セッション ハイジャックや不正な操作の強制につながる可能性があります。
これらの脅威に対抗するには、次の効果的な手法に従ってください。
最上位のドキュメント(最上位フレーム)の場合:
埋め込みドキュメント(iframe)の場合:
両方のドキュメントについて:
Permissions Policy を使用して委任を有効にする
ブラウザでは、クロスオリジンの iframe での WebAuthn へのアクセスはデフォルトでブロックされます。Permissions Policy は、最上位のドキュメントがこれらの強力な機能を特定の信頼できるサードパーティのオリジンに明示的に委任できる、統合されたウェブ プラットフォームのメカニズムです。
機能トークン
WebAuthn では、2 つの異なるトークンが使用されます。
publickey-credentials-get: パスキーのログイン フロー(navigator.credentials.get())の認証を付与します。publickey-credentials-create: パスキー登録フローの認証を付与します(navigator.credentials.create())。
有効化の要件
これらの機能を有効にするには、親サーバーのレスポンスとクライアントサイドのマークアップの両方で調整が必要です。
- Permissions-policy HTTP レスポンス ヘッダー(親サーバーサイト): 親ページは、構造化フィールド構文を使用して、HTTP レスポンス ヘッダーで許可されるオリジンを宣言する必要があります。
Permissions-Policy: publickey-credentials-get=(self "https://embedded-auth.example.com")
権限に関するポリシー: publickey-credentials-get の互換性:
権限に関するポリシー: publickey-credentials-create の互換性:
- HTML の
allow属性: HTML マークアップでは、<iframe>要素もこの機能を有効にすることを宣言する必要があります。
<iframe src="https://embedded-auth.example.com?nonce=deadbeef12345678&client=https%3A%2F%2Fembedded-auth.example.com" allow="publickey-credentials-get"></iframe>
iframe allow="publickey-credentials-get" の互換性:
Browser Support
iframe allow="publickey-credentials-create" の互換性:
Browser Support
パーティション分割されたサードパーティ Cookie を有効にする
信頼性の高い認証フローを確保するには、埋め込みクロスオリジン iframe 内でセッションを確立して維持する必要があります。最新のブラウザがサードパーティ Cookie の厳しい制限に移行するにつれて、標準の永続メカニズムはデフォルトでブロックされることが多く、アクセス権を取得するために Storage Access API の呼び出しが必要になる場合があります。
これらの問題を軽減するには、SameSite:
None、Secure、Partitioned 属性を使用してセッション Cookie を構成します。この統合プラットフォーム メカニズムにより、ブラウザレベルのプライバシー管理を尊重しながら、iframe 内の状態が維持されます。
SameSite: None を設定
SameSite:
None は、クロスサイト アクセス用に Cookie を明示的にマークし、サードパーティのコンテキスト(iframe など)から行われたリクエストで送信できるようにします。この属性は、クロスオリジン シナリオで Cookie が機能するための前提条件ですが、最新のブラウザで受け入れられるようにするには、Secure 属性と組み合わせる必要があります。
Partitioned を設定
Partitioned 属性は、Cookie を CHIPS(Cookies Having Independent Partitioned State)にオプトインし、Cookie をトップレベル サイトごとに個別に保存できるようにします。これにより、Cookie は特定のサードパーティの iframe コンテキスト内で引き続きアクセス可能になり、クロスサイト トラッキングを有効にすることなくセッション状態を維持できます。ユーザーは、別のサイトの埋め込みごとに再度ログインする必要があります。
コンテンツ セキュリティ ポリシーでエンドポイントを保護する
Permissions Policy は iframe で WebAuthn を実行できるかどうかを決定しますが、コンテンツ セキュリティ ポリシー(CSP)は iframe のホストを許可する対象を決定します。
認証エンドポイントでは、承認済みのパートナー サイトまたは独自のプロパティのみがログイン サブフレームを読み込めるようにすることが重要です。これにより、承認されていないクリックジャッキングの試行を、UI を読み込む前にシャットダウンできます。
frame-ancestors を使用する
frame-ancestors ディレクティブは、サイトを埋め込むことができる有効な親ページを定義します。このディレクティブにドメインを追加すると、ログイン サブフレームの埋め込みを許可するドメインを指定できます。
Content-Security-Policy: frame-ancestors 'self' https://parent-site.example.com;
コンテンツ セキュリティ ポリシー: frame-ancestors の互換性:
X-Frame-Options を設定
以前の X-Frame-Options ヘッダーは同様の機能をサポートしていますが、バイナリ オプション(DENY または SAMEORIGIN)のみをサポートしています。ブラウザが CSP をサポートしていない場合に備えて、CSP frame-ancestors と X-Frame-Options: DENY の両方を設定します。CSP は、サポートされている場合は常に優先されます。
X-Frame-Options: DENY
X-Frame-Options の互換性:
サーバーサイドの信頼と検証
ブラウザのクライアントサイド チェックでは意図と権限が評価されますが、信頼の最終的な裁定者はサーバーです。コンテキストが有効で署名されていることを確認するため、RP(Relying Party)サーバーでレスポンスを検証します。
クライアントデータ ペイロード
WebAuthn クライアント データには、iframe 内で行われたリクエストのコンテキストを検証するために特別に設計されたパラメータが含まれています。
crossOrigin(ブール値): WebAuthn API がクロスオリジン iframe 内で呼び出されたかどうかを示します。アーキテクチャが iframe に依存している場合、サーバーは、このフラグがtrueであることを強制する必要があります。topOrigin(文字列): 最上位のブラウジング コンテキストのオリジン(ブラウザのアドレスバーに表示されるもの)。サーバーは、既知の承認済み親オリジンのリストに対してこれを検証する必要があります。
確認チェックリスト
サーバーで認証ツールのレスポンスを検証するには、次の手順を行います。
- 認証ツールのレスポンスから署名付き
collectedClientDataを解析してデコードします。 typeがセレモニー(webauthn.getまたはwebauthn.create)と一致していることを確認します。- ユーザーの存在と署名を確認します。
- リクエストが iframe 構造から送信されることを想定していた場合:
crossOrigin === trueを適用します。topOriginが親オリジンの承認済みリストと一致することを強制します。
postMessage() を使用してセッションを安全に確立する
セッションを確実に確立するには、iframe が postMessage() を使用して認証トークンを親ページに渡す必要があります。これにより、親ページは独自のファーストパーティ コンテキストでセッションの状態を管理できます。
安全なワークフロー
安全なセッションを確立するには、次のワークフローに沿って操作します。
- iframe の
srcURL にnonceとoriginのクエリ パラメータが含まれていることを確認します。nonceにはランダムな値を使用します。nonceは、セキュリティ確認トークンとして機能し、iframe から受け取った認証トークンが、親ページによって開始された特定のセッションと正当に一致することを確認します。originには親フレームのドメインを使用します。originパラメータは親ページのオリジンを指定し、iframe が埋め込まれた承認済みコンテキストを安全に識別できるようにします。
- iframe は独自のサーバーで WebAuthn 認証を完了します。
iframe サーバーは、
nonceを含む JWT などのトークンを発行し、親ページに転送します。// Extract nonce and origin from the URL params const urlParams = new URLSearchParams(window.location.search); const nonce = urlParams.get('nonce'); const origin = urlParams.get('origin'); if (!nonce || !origin) { alert('Nonce or origin is missing in the URL'); return; } // Create a JWT const response = await post('/createToken', { nonce, origin }); const token = response.token; // Post the JWT to the parent frame window.parent.postMessage({ token }, origin);親ページは
messageイベントをリッスンし、送信元のオリジンを検証して、トークンを検証します。window.addEventListener("message", (event) => { if (event.origin !== "https://embedded-auth.example.com") return; // Verify the received JWT const result = await post('/verifyIdToken', { token: event.data.token, origin: provider.origin, }); });JWT が正常に検証されると、親ページはセッションを保持します。
送信者と受信者は、セキュリティの責任を分担します。
- 送信者(iframe): メッセージを送信する際は、常に厳密なターゲット オリジンを指定します(
"*"は使用しません)。 - レシーバ(親): メッセージを受信するときは、常に
event.originを検証して、オリジン なりすましを防ぎます。
まとめ
安全な iframe の使用は、有効化のための Permissions Policy、制限のための CSP、セッションの永続化のためのパーティショニングされたサードパーティ Cookie、クライアント コンテキストのサーバーサイド認証、postMessage() を使用したコンテキストアウェア セッション ハンドオフに依存しています。
関連トピックについて詳しくは、Google の Chrome デベロッパー ブログをご覧ください。また、Chrome デベロッパー ID のドキュメントで、その他のリソースもご確認ください。