身份提供方與 FedCM 整合

本文詳細介紹了身份提供方 (IdP) 與聯邦憑證管理 (FedCM) API 整合所需的所有步驟。

IdP 整合步驟

要與 FedCM 整合,IdP 需要執行以下操作:

  1. 提供一個 well-known 檔案來識別 IdP。
  2. 提供一個配置檔案和端點,用於帳戶列表和斷言頒發(以及可選的客戶端元資料)。
  3. 使用登入狀態 API 更新其登入狀態

提供一個 well-known 檔案

存在一個潛在的隱私問題,即 IdP 能夠在未經明確同意的情況下識別使用者是否訪問了依賴方 (RP)。這具有跟蹤含義,因此 IdP 需要提供一個 well-known 檔案來驗證其身份並緩解此問題。

well-known 檔案是透過非憑證 GET 請求的,該請求不遵循重定向。這有效地阻止了 IdP 瞭解誰發出了請求以及哪個 RP 正在嘗試連線。

well-known 檔案必須從 IdP 的 eTLD+1 處在 /.well-known/web-identity 提供。例如,如果 IdP 端點在 https://accounts.idp.example/ 下提供,則它們必須在 https://idp.example/.well-known/web-identity 處提供一個 well-known 檔案。well-known 檔案的內容應具有以下 JSON 結構:

json
{
  "provider_urls": ["https://accounts.idp.example/config.json"]
}

provider_urls 成員應包含一個 URL 陣列,指向有效的 IdP 配置檔案,RP 可以使用這些檔案與 IdP 互動。陣列長度目前限制為一。

Sec-Fetch-Dest HTTP 標頭

所有從瀏覽器透過 FedCM 傳送的請求都包含 Sec-Fetch-Dest: webidentity 標頭。所有接收憑證請求的 IdP 端點(即 accounts_endpointid_assertion_endpoint)都必須確認包含此標頭,以防範 CSRF 攻擊。

提供一個配置檔案和端點

IdP 配置檔案提供了瀏覽器處理身份聯邦流程和管理登入所需的端點列表。這些端點需要與配置檔案同源。

瀏覽器透過 GET 方法對配置檔案發出非憑證請求,該請求不遵循重定向。這有效地阻止了 IdP 瞭解誰發出了請求以及哪個 RP 正在嘗試連線。

配置檔案(在我們的示例中託管在 https://accounts.idp.example/config.json)應具有以下 JSON 結構:

json
{
  "accounts_endpoint": "/accounts.php",
  "account_label": "developer",
  "supports_use_other_account": true,
  "client_metadata_endpoint": "/client_metadata.php",
  "disconnect_endpoint": "/disconnect.php",
  "id_assertion_endpoint": "/assertion.php",
  "login_url": "/login",
  "branding": {
    "background_color": "green",
    "color": "0xFFEEAA",
    "icons": [
      {
        "url": "https://idp.example/icon.ico",
        "size": 25
      }
    ]
  }
}

屬性如下:

accounts_endpoint

帳戶列表端點的 URL,該端點返回使用者當前在 IdP 上登入的帳戶列表。瀏覽器使用這些帳戶在瀏覽器提供的 FedCM UI 中建立登入選項列表以顯示給使用者。

account_label 可選

如果包含,則指定用於聯合身份驗證時應返回的帳戶子集的識別符號。當發出 get() 請求時,只有在 帳戶端點label_hints 引數匹配此字串的帳戶才會被返回。

supports_use_other_account 可選

一個布林值,預設為 false;如果設定為 true,則表示使用者可以使用與當前登入帳戶不同的帳戶登入(如果 IdP 支援多個帳戶)。這僅適用於指定了 活動模式get() 呼叫。

注意:在瀏覽器登入 UI 中,這很可能表現為某種“選擇其他帳戶”按鈕。

client_metadata_endpoint 可選

客戶端元資料端點的 URL,該端點提供指向 RP 元資料和使用條款頁面的 URL,用於 FedCM UI 中。

disconnect_endpoint 可選

斷開連線端點的 URL,RP 透過 IdentityCredential.disconnect() 方法使用該端點與 IdP 斷開連線。

id_assertion_endpoint

ID 斷言端點的 URL,當傳送有效使用者憑證時,該端點應返回一個驗證令牌,RP 可以使用該令牌驗證身份驗證。

login_url

使用者登入 IdP 的登入頁面 URL。

branding 可選

包含品牌資訊,該資訊將在瀏覽器提供的 FedCM UI 中使用,以根據 IdP 的需要自定義其外觀。在被動模式下,提供的圖示大小必須大於或等於 25 (25px),在活動模式下必須大於或等於 40 (40px)(有關更多詳細資訊,請參閱活動模式與被動模式)。

下表總結了 FedCM API 發出的不同請求:

端點/資源 方法 憑證式(帶 cookie) 包含 Origin
well-known/config.json GET
accounts_endpoint GET
client_metadata_endpoint GET
disconnect_endpoint POST
id_assertion_endpoint POST

注意:有關 FedCM 流程中這些端點訪問的描述,請參閱FedCM 登入流程

注意:出於隱私目的,FedCM API 對此處詳細介紹的端點發出的所有請求都不允許遵循重定向。

帳戶列表端點

瀏覽器透過 GET 方法向此端點發送憑證請求(即,帶有標識已登入使用者的 cookie)。請求沒有 client_id 引數、Origin 標頭或 Referer 標頭。這有效地阻止了 IdP 瞭解使用者正在嘗試登入哪個 RP。返回的帳戶列表是與 RP 無關的。

例如

http
GET /accounts.php HTTP/1.1
Host: idp.example
Accept: application/json
Cookie: 0x23223
Sec-Fetch-Dest: webidentity

成功請求的響應返回使用者當前已登入的所有 IdP 帳戶列表(不特定於任何特定的 RP),其 JSON 結構如下:

json
{
  "accounts": [
    {
      "id": "elaina_maduro",
      "given_name": "Elaina",
      "name": "Elaina Maduro",
      "email": "elaina_maduro@idp.example",
      "tel": "+491234567890",
      "username": "elaina420",
      "picture": "https://idp.example/profile/123",
      "approved_clients": ["123", "456", "789"],
      "domain_hints": ["rp1.example.com", "rp3.example.com"],
      "label_hints": ["developer", "admin"],
      "login_hints": ["elaina_maduro", "elaina_maduro@idp.example"]
    },
    {
      "id": "elly",
      "given_name": "Elly",
      "username": "elly123",
      "email": "Elly@idp.example",
      "picture": "https://idp.example/profile/456",
      "approved_clients": ["abc", "def", "ghi"],
      "domain_hints": ["rp1.example.com", "rp2.example.com"],
      "label_hints": ["developer", "test"],
      "login_hints": ["elly", "elly@idp.example"]
    }
  ]
}

這包括以下資訊,其中 nameemailusernametel 是可選的,但其中至少一個必須存在且非空。

id

使用者的唯一 ID。

name 可選

使用者的姓氏。

email 可選

使用者的電子郵件地址。

tel 可選

使用者的電話號碼。可以是任何格式。

username 可選

使用者的使用者名稱。

given_name 可選

使用者的名字。

picture 可選

使用者頭像圖片的 URL。

approved_clients 可選

使用者已註冊的 RP 客戶端陣列。

domain_hints 可選

與帳戶關聯的域陣列。RP 可以發出包含 domainHint 屬性的 get() 呼叫,以按域篩選返回的帳戶。

label_hints 可選

指定標識帳戶型別的標籤字串陣列。如果配置檔案指定了 account_label,則只有在其 label_hints 中包含該標籤的帳戶才會從帳戶列表端點返回。

login_hints 可選

表示帳戶的字串陣列。這些字串用於篩選瀏覽器為使用者登入提供的帳戶選項列表。當在相關的 get() 呼叫中 identity.providers 中提供了 loginHint 屬性時,會發生這種情況。任何在其 login_hints 陣列中包含與提供的 loginHint 匹配的字串的帳戶都將包含在內。

注意:如果使用者未登入任何 IdP 帳戶,則端點應返回 HTTP 401 (Unauthorized)

客戶端元資料端點

瀏覽器透過 GET 方法向此端點發送非憑證請求,並將 clientId 作為引數傳遞到 get() 呼叫中。

例如

http
GET /client_metadata.php?client_id=1234 HTTP/1.1
Host: idp.example
Origin: https://rp.example/
Accept: application/json
Sec-Fetch-Dest: webidentity

成功請求的響應包含指向 RP 元資料和使用條款頁面的 URL,用於瀏覽器提供的 FedCM UI。這應遵循以下 JSON 結構:

json
{
  "privacy_policy_url": "https://rp.example/privacy_policy.html",
  "terms_of_service_url": "https://rp.example/terms_of_service.html"
}

斷開連線端點

透過呼叫 IdentityCredential.disconnect(),瀏覽器向斷開連線端點發送一個跨域 POST 請求,其中包含 cookie 和 application/x-www-form-urlencodedContent-Type,以及以下資訊:

account_hint

一個字串,指定 IdP 用於識別要斷開連線的帳戶的帳戶提示。

client_id

一個字串,指定 RP 的客戶端識別符號。

例如

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

account_hint=account456&client_id=rp123

收到請求後,IdP 伺服器應:

  1. 使用 CORS(跨域資源共享)響應請求。

  2. 驗證請求是否包含帶有 webidentity 指令的 Sec-Fetch-Dest HTTP 標頭。

  3. Origin 標頭與由 client_id 確定的 RP 源匹配。如果不匹配,則拒絕 promise。

  4. 查詢與 account_hint 匹配的帳戶。

  5. 將使用者帳戶從 RP 連線帳戶列表中斷開連線。

  6. 以 JSON 格式響應已識別使用者的 account_id

    json
    {
      "account_id": "account456"
    }
    

注意:如果 IdP 希望斷開與 RP 關聯的所有帳戶,它可以傳遞一個不匹配任何 account_id 的字串,例如 "account_id": "*"

ID 斷言端點

瀏覽器透過 POST 方法向此端點發送憑證請求,內容型別為 application/x-www-form-urlencoded。請求還包含一個負載,其中包括有關嘗試登入和要驗證的帳戶的詳細資訊。

它應該看起來像這樣:

http
POST /assertion.php 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=Ct60bD&disclosure_text_shown=true&is_auto_selected=true

當用戶從相關的瀏覽器 UI 中選擇一個帳戶進行登入時,會向此端點發送請求。當傳送有效使用者憑證時,此端點應返回一個驗證令牌,RP 可以根據他們用於身份聯邦的 IdP 概述的使用說明,在自己的伺服器上使用該令牌驗證身份驗證。一旦 RP 驗證了使用者,他們就可以登入使用者、將他們註冊到其服務等等。

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

請求負載包含以下引數:

client_id

RP 的客戶端識別符號(與原始 get() 請求中的 clientId 匹配)。

account_id

要登入的使用者帳戶的唯一 ID(與帳戶列表端點響應中使用者的 id 匹配)。

nonce 可選

由 RP 提供的請求隨機數。

disclosure_text_shown

一個字串,值為 "true""false",指示是否顯示了披露文字。披露文字是向用戶顯示的資訊(如果提供了,可以包括服務條款和隱私政策連結),如果使用者已登入到 IdP 但在當前 RP 上沒有特定帳戶(在這種情況下,他們需要選擇“繼續作為...”其 IdP 身份,然後在 RP 上建立相應的帳戶)。

is_auto_selected

一個字串,值為 "true""false",指示身份驗證驗證請求是否由於自動重新身份驗證而發出,即沒有使用者干預。當 get() 呼叫以 "optional""silent"mediation 選項值發出時,可能會發生這種情況。對於 IdP 來說,瞭解是否發生了自動重新身份驗證對於效能評估和在需要更高安全性時很有用。例如,IdP 可以返回一個錯誤程式碼,告訴 RP 它需要明確的使用者干預(mediation="required")。

注意:如果 get() 呼叫成功,則 is_auto_selected 值也會透過 IdentityCredential.isAutoSelected 屬性傳達給 RP。

ID 斷言端點的 CORS 標頭

ID 斷言端點響應必須包含 Access-Control-Allow-OriginAccess-Control-Allow-Credentials 標頭,並且 Access-Control-Allow-Origin 必須包含請求者的源:

http
Access-Control-Allow-Origin: https://rp.example
Access-Control-Allow-Credentials: true

請注意,Access-Control-Allow-Origin 必須設定為請求者(RP)的特定源,並且不能是萬用字元值 *

如果沒有這些標頭,請求將因網路錯誤而失敗。

ID 斷言錯誤響應

如果 IdP 無法頒發令牌(例如,如果客戶端未經授權),ID 斷言端點將返回一個錯誤響應,其中包含有關錯誤性質的資訊。例如:

json
{
  "error": {
    "code": "access_denied",
    "url": "https://idp.example/error?type=access_denied"
  }
}

錯誤響應欄位如下:

code 可選

一個字串。這可以是 OAuth 2.0 指定錯誤列表 中的已知錯誤,也可以是任意字串。

url 可選

一個 URL。這應該是一個網頁,其中包含有關錯誤的人類可讀資訊,以便顯示給使用者,例如如何修復錯誤或聯絡客戶服務。URL 必須與 IdP 的配置 URL 同站點。

此資訊可以通過幾種不同的方式使用:

  • 瀏覽器可以向用戶顯示自定義 UI,告知他們出了什麼問題(請參閱Chrome 文件以獲取示例)。請記住,如果請求因 IdP 伺服器不可用而失敗,它顯然無法返回任何資訊。在這種情況下,瀏覽器將透過通用訊息報告此情況。
  • 用於嘗試登入的相關 RP navigator.credentials.get() 呼叫將拒絕其 promise,並帶有一個包含錯誤資訊的 IdentityCredentialError。RP 可以捕獲此錯誤,然後透過一些資訊來幫助使用者在未來的登入嘗試中成功,從而跟進瀏覽器的自定義 UI。

使用登入狀態 API 更新登入狀態

登入狀態 API 允許 IdP 通知瀏覽器它在該特定瀏覽器中的登入(登入)狀態 — 透過此,我們指的是“是否有任何使用者在該瀏覽器中登入到 IdP”。瀏覽器為每個 IdP 儲存此狀態;FedCM API 隨後使用它來減少向 IdP 發出的請求數量(因為它無需在沒有使用者登入到 IdP 時浪費時間請求帳戶)。它還緩解了潛在的時序攻擊

對於每個已知的 IdP(由其配置 URL 標識),瀏覽器都維護一個表示登入狀態的三態變數,具有三個可能的值:

  • "logged-in":IdP 至少有一個使用者帳戶已登入。請注意,在此階段,RP 和瀏覽器不知道是哪個使用者。有關特定使用者的資訊將在 FedCM 流程的後期從 IdP 的 accounts_endpoint 返回。
  • "logged-out":所有 IdP 帳戶當前都已登出。
  • "unknown":此 IdP 的登入狀態未知。這是預設值。

設定登入狀態

當用戶登入或登出 IdP 時,IdP 應更新其登入狀態。這可以透過兩種不同的方式完成:

  • Set-Login HTTP 響應標頭可以在頂級導航或同源子資源請求中設定:

    http
    Set-Login: logged-in
    
    Set-Login: logged-out
    
  • Navigator.login.setStatus() 方法可以從 IdP 源呼叫:

    js
    /* Set logged-in status */
    navigator.login.setStatus("logged-in");
    
    /* Set logged-out status */
    navigator.login.setStatus("logged-out");
    

登入狀態如何影響聯合登入流程

RP 嘗試聯合登入時,會檢查登入狀態:

  • 如果 IdP 的登入狀態為 "logged-in",則會向帳戶列表端點發出請求,並在瀏覽器提供的 FedCM 對話方塊中向用戶顯示可用於登入的帳戶。
  • 如果所有 IdP 的登入狀態都為 "logged-out",則 FedCM get() 請求返回的 promise 將被拒絕,而不會向帳戶列表端點發出請求。在這種情況下,由開發人員處理流程,例如提示使用者去登入合適的 IdP。
  • 如果 IdP 的登入狀態為 "unknown",則會向帳戶列表端點發出請求,並且登入狀態會根據響應進行更新:
    • 如果端點返回可用於登入的帳戶列表,則將狀態更新為 "logged-in",並在瀏覽器提供的 FedCM 對話方塊中向用戶顯示登入選項。
    • 如果端點不返回任何帳戶,則將狀態更新為 "logged-out";如果其他 logged-in IdP 不可用,FedCM get() 請求返回的 promise 將被拒絕。

如果瀏覽器和 IdP 登入狀態不同步怎麼辦?

儘管登入狀態 API 通知瀏覽器 IdP 登入狀態,但瀏覽器和 IdP 有可能不同步。例如,IdP 會話可能會過期,這意味著所有使用者帳戶都已登出,但登入狀態仍然設定為 "logged-in"(應用程式無法將登入狀態設定為 "logged-out")。在這種情況下,當嘗試聯合登入時,將向 IdP 的帳戶列表端點發出請求,但由於會話不再可用,將不會返回任何可用帳戶。

發生這種情況時,瀏覽器可以透過在對話方塊中開啟 IdP 的登入頁面來動態地讓使用者登入 IdP(登入 URL 在 IdP 的配置檔案 login_url 中找到)。此流程的具體性質取決於瀏覽器;例如,Chrome 以這種方式處理它

使用者登入 IdP 後,IdP 應:

另見