FedCM の更新: Continuation API バンドルのオリジン トライアルと Storage Access API の自動付与

Chrome 126 以降、デベロッパーは一部の承認ユースケースを有効にするデスクトップ Federated Credential Management API(FedCM)機能のバンドルのオリジン トライアルを開始できます。このバンドルは Continuation API と Parameters API で構成されます。これらの API は、ID プロバイダ(IdP)が提供する権限ダイアログで OAuth 認証フローに似たエクスペリエンスを実現します。このバンドルには、Fields API、複数の configURL、カスタム アカウント ラベルなど、その他の変更も含まれています。Chrome 126 以降では、Storage Access API(SAA)のオリジン トライアルも導入しています。これは、ユーザーが過去に FedCM を使用して正常にログインした場合に SAA リクエストを自動的に付与するものです。

オリジン トライアル: FedCM Continuation API バンドル

FedCM Continuation API バンドルは、複数の FedCM 拡張機能で構成されています。

Continuation API

ユーザーが RP にログインしてから、ボタンモードで承認します。

Glitch で API を使用するデモをご覧いただけます。

Continuation API を使用すると、IdP の ID アサーション エンドポイントは必要に応じて FedCM がレンダリングする URL を返し、ユーザーがマルチステップのログインフローを続行できるようになります。これにより、IdP は、既存の FedCM UI で可能な以上の権限(ユーザーのサーバーサイド リソースへのアクセスなど)をリライング パーティ(RP)に付与するようユーザーにリクエストできます。

通常、ID アサーション エンドポイントは認証に必要なトークンを返します。

{
  "token": "***********"
}

これに対して Continuation API では、ID アサーション エンドポイントは、ID アサーション エンドポイントへの絶対パスまたは相対パスを含む continue_on プロパティを返すことができます。

{
  // In the id_assertion_endpoint, instead of returning a typical
  // "token" response, the IdP decides that it needs the user to
  // continue on a pop-up window:
  "continue_on": "/oauth/authorize?scope=..."
}

ブラウザが continue_on レスポンスを受信するとすぐに、新しいポップアップ ウィンドウが開き、指定されたパスにユーザーを誘導します。

ユーザーがページを操作すると(たとえば、RP と追加情報を共有する権限を付与するなど)、IdP ページは IdentityProvider.resolve() を呼び出して元の navigator.credentials.get() 呼び出しを解決し、トークンを引数として返すことができます。

document.getElementById('allow_btn').addEventListener('click', async () => {
  let accessToken = await fetch('/generate_access_token.cgi');
  // Closes the window and resolves the promise (that is still hanging
  // in the relying party's renderer) with the value that is passed.
  IdentityProvider.resolve(accessToken);
});

ブラウザはポップアップを自ら閉じ、API 呼び出し元にトークンを返します。

ユーザーがリクエストを拒否した場合は、IdentityProvider.close() を呼び出してウィンドウを閉じることができます。

IdentityProvider.close();

なんらかの理由でユーザーがポップアップでアカウントを変更した場合(たとえば、IdP が「ユーザーの切り替え」機能を提供している場合や、委任の場合)、Resolve の呼び出しは、次のようなオプションの 2 番目の引数を取ります。

IdentityProvider.resolve(token, {accountId: '1234');

パラメータ API

Parameters API を使用すると、RP は ID アサーション エンドポイントに追加のパラメータを提供できます。Parameters API を使用すると、RP は追加のパラメータを IdP に渡して、基本的なログイン以外のリソースの権限をリクエストできます。ユーザーは、Continuation API を介して開始される IdP 制御の UX フローを通じてこれらの権限を承認します。

API を使用するには、navigator.credentials.get() 呼び出しのオブジェクトとして params プロパティにパラメータを追加します。

let {token} = await navigator.credentials.get({
  identity: {
    providers: [{
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      // Key/value pairs that need to be passed from the
      // RP to the IdP but that don't really play any role with
      // the browser.
      params: {
        IDP_SPECIFIC_PARAM: '1',
        foo: 'BAR',
        ETC: 'MOAR',
        scope: 'calendar.readonly photos.write',
      }
    },
  }
});

params オブジェクトのプロパティ名の先頭には param_ が付加されます。上記の例で、params プロパティには、IDP_SPECIFIC_PARAM'1'foo'BAR'ETC'MOAR'scope'calendar.readonly photos.write' として含まれています。これは、リクエストの HTTP 本文で param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write に変換されます。

POST /fedcm_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false&param_IDP_SPECIFIC_PARAM=1&param_foo=BAR&param_ETC=MOAR&param_scope=calendar.readonly%20photos.write

権限を動的に取得する

一般に、ユーザーにとっては、デベロッパーが実装が最も簡単だと感じているときよりも、必要なときに権限をリクエストする方が役に立ちます。たとえば、ユーザーが写真を撮る際にカメラへのアクセス許可を求めるほうが、ウェブサイトにアクセスしたらすぐに許可を求めるよりも推奨されます。サーバー リソースについても同じことが言えます。権限は、ユーザーが必要とする場合にのみリクエストします。これは「動的承認」と呼ばれます。

FedCM を使用して動的に認可をリクエストするには、IdP で次の操作を行うことができます。

  1. IdP が認識できる必須パラメータ(scope など)を使用して navigator.credentials.get() を呼び出します。
  2. ID アサーション エンドポイントは、ユーザーがすでにログインしていることを確認し、continue_on URL を返します。
  3. ブラウザでポップアップ ウィンドウが開き、IdP の権限ページで、リクエストされたスコープに一致する追加の権限を求めるメッセージが表示されます。
  4. IdP によって IdentityProvider.resolve() を介して認可されると、ウィンドウは閉じ、RP の元の navigator.credentials.get() 呼び出しが関連トークンまたは認証コードを取得し、RP が適切なアクセス トークンと交換できるようになります。

フィールド API

Fields API を使用すると、RP が IdP にリクエストするアカウント属性を宣言できます。これにより、ブラウザは FedCM ダイアログに適切な開示 UI をレンダリングできます。返されるトークンにリクエストされたフィールドを含めるのは IdP の責任です。OpenID Connect では「基本的なプロファイル」をリクエストする場合と、OAuth では「スコープ」をリクエストする場合とを比較します。

ウィジェット モードの開示メッセージ。
ウィジェット モードの開示メッセージ。
ボタンモードの開示メッセージ。
ボタンモードの開示メッセージ。

Fields API を使用するには、navigator.credentials.get() 呼び出し内の配列として fields プロパティにパラメータを追加します。現時点では、フィールドに 'name''email''picture' を含めることができますが、今後他の値を含めるように拡張できます。

fields を使用したリクエストは次のようになります。

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      fields: ['name', 'email', 'picture'],
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      params: {
        scope: 'drive.readonly calendar.readonly',
      }
    },
  }
  mediation: 'optional',
});

ID アサーション エンドポイントへの HTTP リクエストには、RP で指定された fields パラメータが含まれます。リピーターでない場合は disclosure_text_shown パラメータが true に設定されます。また、disclosure_shown_for パラメータでブラウザがユーザーに開示したフィールドが含まれます。

POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=true&fields=email,name,picture&disclosure_shown_for=email,name,picture

IdP からの追加データ(カレンダーへのアクセスなど)に RP がアクセスする必要がある場合は、前述のカスタム パラメータを使用して処理する必要があります。IdP は continue_on URL を返して権限をリクエストします。

fields が空の配列の場合、リクエストは次のようになります。

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      fields: [],
      clientId: '1234',
      configURL: 'https://idp.example/fedcm.json',
      params: {
        scope: 'drive.readonly calendar.readonly',
      }
    },
  }
  mediation: 'optional',
});

fields に空の配列を指定した場合、ユーザー エージェントは開示 UI をスキップします。

ウィジェット モードでは開示メッセージは表示されません。ボタンフローでは、開示 UI は完全にスキップされます。
ウィジェット モードでは開示メッセージが表示されません。ボタンフローでは、開示 UI は完全にスキップされます。

これは、アカウント エンドポイントからのレスポンスに、approved_clients の RP と一致するクライアント ID が含まれていない場合でも同様です。

この場合、ID アサーション エンドポイントに送信される disclosure_text_shown は、HTTP 本文で false になります。

POST /id_assertion_endpoint HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Content-Type: application/x-www-form-urlencoded
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

account_id=123&client_id=client1234&nonce=234234&disclosure_text_shown=false

複数の configURL

複数の configURL を使用すると、IdP は構成ファイルと同じ well-known ファイルaccounts_endpointlogin_url を指定することで、IdP の複数の構成ファイルに対応できます。

accounts_endpointlogin_url が well-known ファイルに追加されると、provider_urls は無視されるため、IdP は複数の構成ファイルをサポートできます。互換性がない場合でも、provider_urls は引き続き有効になり、下位互換性が確保されます。

複数の configURL をサポートする広く知られたファイルは、次のようになります。

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

このため、次のことを実現できる。

  1. 既存のよく知られたファイルと、すでにデプロイされている以前のバージョンのブラウザとの下位互換性と上位互換性を維持します。
  2. 任意の数の構成ファイルを使用できます。ただし、すべての構成ファイルが同じ accounts_endpointlogin_url を指している必要があります。
  3. accounts_endpoint に対する認証情報取得リクエストでは「.well-known」レベルで指定する必要があるため、エントロピーが追加されることはありません。

複数の configURL のサポートは任意です。既存の FedCM 実装は同じままでかまいません。

カスタム アカウント ラベル

カスタム アカウント ラベルを使用すると、FedCM IdP でアカウントにアノテーションを付けることができ、構成ファイルでラベルを指定して RP がアカウントをフィルタリングできるようになります。Domain Hints APILogin Hint API を使用して navigator.credentials.get() 呼び出しで指定することで同様のフィルタリングを行うこともできますが、カスタム アカウント ラベルでは、構成ファイルを指定することでユーザーをフィルタできます。これは、複数の configURL を使用する場合に特に有用です。カスタム アカウント ラベルは、ログインやドメインヒントなど、RP からではなく IdP サーバーから提供される点も異なります。

IdP は、コンシューマ用とエンタープライズ用にそれぞれ 2 つの configURL をサポートしています。コンシューマ構成ファイルには 'consumer' ラベルがあり、エンタープライズ構成ファイルには 'enterprise' ラベルがあります。

このような設定では、well-known ファイルに accounts_endpointlogin_url が含まれ、複数の configURL を使用できます。

{
  "provider_urls": [ "https://idp.example/fedcm.json" ],
  "accounts_endpoint": "https://idp.example/accounts",
  "login_url": "https://idp.example/login"
}

accounts_endpoint が well-known ファイルで指定されている場合、provider_urls は無視されます。RP は、navigator.credentials.get() 呼び出しでそれぞれの構成ファイルを直接参照できます。

コンシューマの構成ファイルは https://idp.example/fedcm.json にあり、include を使用して 'consumer' を指定する accounts プロパティが含まれています。

{
  "accounts_endpoint": "https://idp.example/accounts",
  "client_metadata_endpoint": "/client_metadata",
  "login_url": "https://idp.example/login",
  "id_assertion_endpoint": "/assertion",
  "accounts": {
    "include": "consumer"
  }
}

エンタープライズ構成ファイルは https://idp.example/enterprise/fedcm.json にあります。これには、include を使用して 'enterprise' を指定する accounts プロパティが含まれています。

{
  "accounts_endpoint": "https://idp.example/accounts",
  "client_metadata_endpoint": "/enterprise/client_metadata",
  "login_url": "https://idp.example/login",
  "id_assertion_endpoint": "/assertion",
  "accounts": {
    "include": "enterprise"
  }
}

共通の IdP アカウント エンドポイント(この例では https://idp.example/accounts)は、アカウントごとに配列に labels が割り当てられたラベル プロパティを含むアカウントのリストを返します。以下は、2 つのアカウントを持つユーザーに対するレスポンスの例です。1 つは一般ユーザー向け、もう 1 つはエンタープライズ向けです。

{
 "accounts": [{
   "id": "123",
   "given_name": "John",
   "name": "John Doe",
   "email": "john_doe@idp.example",
   "picture": "https://idp.example/profile/123",
   "labels": ["consumer"]
  }], [{
   "id": "4567",
   "given_name": "Jane",
   "name": "Jane Doe",
   "email": "jane_doe@idp.example",
   "picture": "https://idp.example/profile/4567",
   "labels": ["enterprise"]
  }]
}

RP が 'enterprise' ユーザーにログインを許可する場合、navigator.credentials.get() 呼び出しで 'enterprise' configURL 'https://idp.example/enterprise/fedcm.json' を指定できます。

let { token } = await navigator.credentials.get({
  identity: {
    providers: [{
      clientId: '1234',
      nonce: '234234',
      configURL: 'https://idp.example/enterprise/fedcm.json',
    },
  }
});

そのため、ユーザーがログインに使用できるのはアカウント ID '4567' のみです。アカウント ID '123' はブラウザによって通知なく非表示になるため、このサイトの IdP でサポートされていないアカウントがユーザーに提供されることはありません。

オリジン トライアル: Storage Access API のトラスト シグナルとしての FedCM

Chrome 126 では、Storage Access API のトラスト シグナルとしての FedCM のオリジン トライアルが開始されます。この変更により、FedCM による事前の付与が、Storage Access API によるストレージ アクセス リクエストを自動的に承認する正当な理由になります。

これは、埋め込み iframe がパーソナライズされたリソースにアクセスする必要がある場合に役立ちます。たとえば、idp.example が rp.example に埋め込まれていて、パーソナライズされたリソースを表示する必要がある場合です。ブラウザがサードパーティ Cookie へのアクセスを制限している場合、ユーザーが FedCM で idp.example を使用して rp.example にログインしていても、埋め込まれた idp.example iframe はパーソナライズされたリソースをリクエストできません(リクエストにサードパーティ Cookie は含まれないため)。

これを実現するには、idp.example はウェブサイトに埋め込まれた iframe を介してストレージ アクセス権限を取得する必要があります。これは、権限プロンプトからのみ取得できます。

FedCM を Storage Access API のトラスト シグナルとして使用すると、Storage Access API の権限チェックは、ストレージ アクセス プロンプトによって指定された権限付与だけでなく、FedCM プロンプトによって指定された権限の付与も承認します。

// In top-level rp.example:

// Ensure FedCM permission has been granted.
const cred = await navigator.credentials.get({
  identity: {
    providers: [{
      configURL: 'https://idp.example/fedcm.json',
      clientId: '123',
    }],
  },
  mediation: 'optional',
});

// In an embedded IdP iframe:

// No user gesture is needed to call this, and the call will be auto-granted.
await document.requestStorageAccess();

// This returns `true`.
const hasAccess = await document.hasStorageAccess();

ユーザーが FedCM でログインすると、FedCM 認証が有効である限り、権限が自動的に付与されます。つまり、ユーザーの接続が解除されると、権限をリクエストするとメッセージが表示されます。

オリジン トライアルに参加する

Chrome 126 以降で Chrome フラグ chrome://flags#fedcm-authz を有効にすると、FedCM Continuation API バンドルをローカルで試すことができます。Chrome 126 以降で #fedcm-with-storage-access-api を有効にして、FedCM を Storage Access API のトラスト シグナルとしてローカルで試すこともできます。

これらの機能はオリジン トライアルとしても利用できます。オリジン トライアルでは、新機能を試して、ユーザビリティ、実用性、有効性に関するフィードバックを提供できます。詳しくは、オリジン トライアルのスタートガイドをご覧ください。

FedCM Continuation API バンドルのオリジン トライアルを試すには、2 つのオリジン トライアル トークンを作成します。

ボタンフローとともに Continuation API を有効にする場合は、Button Mode API オリジン トライアルも有効にします。

Storage Access API オリジン トライアルでトラスト シグナルとして FedCM を使用するには:

Continuation API バンドルのオリジン トライアルと、Storage Access API のオリジン トライアルのトラスト シグナルとしての FedCM は、Chrome 126 以降で利用できます。

RP にサードパーティのオリジン トライアルを登録する

  1. オリジン トライアルの登録ページに移動します。
  2. [Register] ボタンをクリックし、フォームに記入してトークンをリクエストします。
  3. IdP の生成元を [ウェブ生成元] として入力します。
  4. 他のオリジンで JavaScript を使用してトークンを挿入する場合は、[サードパーティ マッチング] チェックボックスをオンにします。
  5. [送信] をクリックします。
  6. 発行されたトークンをサードパーティのウェブサイトに埋め込みます。

サードパーティのウェブサイトにトークンを埋め込むには、IdP の送信元から提供される IdP の JavaScript ライブラリまたは SDK に次のコードを追加します。

const tokenElement = document.createElement('meta');
tokenElement.httpEquiv = 'origin-trial';
tokenElement.content = 'TOKEN_GOES_HERE';
document.head.appendChild(tokenElement);

TOKEN_GOES_HERE は、独自のトークンに置き換えます。