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

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. エントロピーは「.well-known」レベルで指定する必要があるため、accounts_endpoint に対して行われた認証情報取得リクエストにエントロピーを追加する機会がありません。

複数の 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"
}

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 にログインしていても、埋め込まれた 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. [登録] ボタンをクリックし、フォームに記入してトークンをリクエストします。
  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 は、独自のトークンに置き換えます。