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

Chrome 126 以降、デベロッパーは、一部の認可ユースケースを可能にするデスクトップ Federated Credential Management API(FedCM)機能のバンドルのオリジン トライアルを開始できます。このバンドルは、Continuation API と Parameters 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');

Parameters 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 が適切なアクセス トークンと交換できるようになります。

Fields 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

RP が IdP の追加データ(カレンダーへのアクセスなど)にアクセスする必要がある場合は、上記のカスタム パラメータで処理する必要があります。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 をサポートする well-known ファイルは次のようになります。

{
  "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 Hint 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"
}

well-known ファイルで accounts_endpoint が指定されている場合、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 にログインしている場合でも、リクエストにサードパーティ Cookie が含まれないため、埋め込まれた idp.example iframe はパーソナライズされたリソースをリクエストできません。

これを実現するには、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 をオンにすると、Storage Access API の信頼シグナルとして FedCM をローカルで試すこともできます。

これらの機能はオリジン トライアルとしてもご利用いただけます。オリジン トライアルでは、新機能を試して、その使いやすさ、実用性、効果についてフィードバックを送信できます。詳細については、起点のトライアルの開始をご覧ください。

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

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

Storage Access API オリジン トライアルの信頼シグナルとして FedCM を試すには:

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

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

  1. オリジン トライアルの登録ページに移動します。
  2. [登録] ボタンをクリックし、フォームに記入してトークンをリクエストします。
  3. IdP のオリジンを [Web Origin] として入力します。
  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 は、独自のトークンに置き換えます。