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 Continuation API 套裝組合

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

Continuation API

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

您可以查看 Glitch 上的 API 示範

Continuation API 可讓 IdP 的 ID 斷言端點視需要傳回 FedCM 顯示的網址,讓使用者繼續執行多步驟登入流程。這可讓 IdP 要求使用者授予信賴方 (RP) 權限,超出現有 FedCM UI 的範圍,例如存取使用者的伺服器端資源。

一般而言,ID 斷言端點會傳回驗證所需的權杖。

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

然而,使用 Continuation 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');

參數 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。

即使帳戶端點的回應不含與 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. accounts_endpoint 發出的憑證擷取要求擷取要求沒有機會,無法加入熵,因為必須在「.well-known」層級指定。

您可以選擇是否要支援多個 configURL,且現有的 FedCM 實作項目可以維持不變。

自訂帳戶標籤

自訂帳戶標籤可讓 FedCM IdP 為帳戶加註,讓 RP 在設定檔中指定標籤來篩選帳戶。透過 Domain Hint APILogin Hint API 也可透過在 navigator.credentials.get() 呼叫中指定設定檔來篩選使用者,不過自訂帳戶標籤可在使用多個 configURLs 時特別實用。「自訂帳戶標籤」也不同,因為自訂帳戶標籤是由 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 時,系統會忽略 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,其中包括 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"
  }
}

常見的 IdP 帳戶端點 (在此範例中為 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 Continuation API 套件。您也可以在 Chrome 126 以上版本開啟 #fedcm-with-storage-access-api,以在本機使用 FedCM 做為 Storage Access API 的信任信號。

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

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

如果您有意啟用 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 替換成您自己的權杖。