FedCM 更新:Continuation API 套裝組合的來源試用和 Storage Access API 自動授權

自 Chrome 126 起,開發人員可以開始試用一系列電腦版 Federated Credential Management API (FedCM) 功能,可用於某些授權用途。套裝組合包含 Continuation API 和 Parameters API,這些 API 可以啟用類似 OAuth 授權流程的體驗,涉及識別資訊提供者 (IdP) 提供的權限對話方塊。套件也包含其他變更,例如 Fields API、多個 configURL 和自訂帳戶標籤。自 Chrome 126 起,我們也為 Storage Access API (SAA) 推出了來源測試,如果使用者先前曾成功使用 FedCM 登入,系統就會自動核准 SAA 要求。

來源試用:FedCM 接續 API 套件

FedCM Continuation API 組合包含多個 FedCM 擴充功能:

Continuation API

使用者要登入 RP,然後透過按鈕模式授權。

您可以查看 Glitch 上的 API 示範

Continuation API 可讓 IdP 的 ID 斷言端點 選擇傳回 FedCM 會轉譯的網址,讓使用者繼續多步驟登入流程。這可讓 IdP 要求使用者授予依附方 (RP) 權限,超出現有 FedCM UI 中可用的權限,例如存取使用者的伺服器端資源。

通常,ID 斷言端點會傳回驗證所需的權杖。

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

不過,透過接續 API,ID 斷言端點可以傳回 continue_on 屬性,其中包含 ID 斷言端點的絕對路徑或相對路徑。

{
  // 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 提供「切換使用者」功能,或在委派案例中),則解析呼叫會採用可選的第二個引數,允許以下操作:

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

Parameters API

Parameters API 可讓 RP 為 ID 斷言端點提供其他參數。透過 Parameters API,RP API 可以將其他參數傳遞給 IdP,要求基本登入以外的資源權限。使用者可透過透過 Continuation API 啟動的 IdP 控管使用者體驗流程,授予這些權限。

如要使用 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_。在上述範例中,參數屬性包含 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 可以理解的必要參數呼叫 navigator.credentials.get(),例如 scope
  2. ID 斷言端點會確認使用者已登入,並以 continue_on 網址回應。
  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

如果 RP 需要存取 IdP 提供的任何額外資料 (例如日曆存取權),請使用上述自訂參數來處理。IdP 會傳回 continue_on 網址以要求權限。

如果 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。

即使 accounts 端點的回應不含與 approved_clients 中的 RP 相符的客戶端 ID,也會發生這種情況。

在這種情況下,傳送至 ID 斷言端點disclosure_text_shown 在 HTTP 主體中為否:

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 容納 IdP 的多個設定檔,方法是在已知檔案與設定檔相同中指定 accounts_endpointlogin_url

如果將 accounts_endpointlogin_url 加入已知檔案,系統會忽略 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 Hint APILogin Hint API,在 navigator.credentials.get() 呼叫中指定這兩個 API,以便進行類似的篩選,但自訂帳戶標籤可以透過指定設定檔來篩選使用者,這在使用多個 configURL 時特別實用。「自訂帳戶標籤」也不同,因為自訂帳戶標籤是由 IdP 伺服器提供,與 RP 不同,例如登入或網域提示。

範例

IdP 可分別支援消費者和企業的兩個 configURL。用戶設定檔具有 'consumer' 標籤,而企業設定檔有 'enterprise' 標籤。

透過這種設定,知名檔案會包含 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,其中包含 accounts 屬性,可使用 include 指定 'consumer'

{
  "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,其中包括 accounts 屬性,該屬性會使用 include 指定 'enterprise'

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

常見的 ID 提供者 帳戶端點 (在本例中為 https://idp.example/accounts) 會傳回帳戶清單,其中包含標籤屬性,並在每個帳戶的陣列中指派 labels。以下是使用者擁有兩個帳戶的回應範例。一種適用於消費者,另一種則適用於企業:

{
 "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',
    },
  }
});

因此,使用者只能使用 '4567' 的帳戶 ID 登入。瀏覽器會以未顯示訊息的方式隱藏 '123' 的帳戶 ID,以免這個網站上的 IdP 不支援使用者的帳戶。

來源試用:FedCM 做為 Storage Access API 的信任信號

Chrome 126 正在開始來源試用,將 FedCM 做為 Storage Access API 的信任信號。這項異動後,透過 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 接續 API 套件。您也可以在 Chrome 126 以上版本中開啟 #fedcm-with-storage-access-api,將 FedCM 當做 Storage Access API 的本機信任訊號。

這些功能也可做為來源試用。來源試用計畫可讓你試用新功能,並就可用性、實用性和成效提供意見回饋。詳情請參閱「開始使用來源試用」一文。

如要試用 FedCM Continuation API bundle 來源試用,請建立兩個來源試用權杖:

如果您想啟用 Continuation API 和按鈕流程,請一併啟用按鈕模式 API 來源測試版

如要試用 FedCM 做為 Storage Access API 來源試用的信任信號

Continuation API 套件來源試用和 FedCM 可做為 Storage Access API 來源試用的信任信號,皆可從 Chrome 126 開始使用。

為 RP 註冊第三方來源試用

  1. 前往原始測試註冊頁面。
  2. 按一下「Register」按鈕,並填寫表單以要求權杖。
  3. 將 IdP 的來源輸入為「Web Origin」
  4. 檢查第三方比對,透過 JavaScript 在其他來源插入權杖。
  5. 按一下「提交」
  6. 將核發的權杖嵌入第三方網站。

如要將權杖嵌入第三方網站,請將下列程式碼新增至 IdP 的 JavaScript 程式庫或從 IdP 來源提供的 SDK。

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

TOKEN_GOES_HERE 替換成您自己的權杖。