身份提供者与 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 成员应包含一个指向有效 IdP 配置文件的 URL 数组,RP 可以使用这些文件与 IdP 交互。数组长度目前限制为一个。

Sec-Fetch-Dest HTTP 标头

浏览器通过 FedCM 发送的所有请求都包含一个 Sec-Fetch-Dest: webidentity 标头。接收凭据化请求(即 accounts_endpointid_assertion_endpoint)的所有 IdP 端点都必须确认此标头已包含在内,以防止 CSRF 攻击。

提供一个配置文件和端点

IdP 配置文件提供了一个端点列表,浏览器需要使用这些端点来处理身份联合流程并管理登录。这些端点需要与配置文件具有相同的来源。

浏览器通过 GET 方法对配置文件发出一个非凭据化请求,该请求不遵循重定向。这有效地阻止 IdP 了解谁发出了请求以及哪个 RP 正在尝试连接。

配置文件(在我们示例中托管在 https://accounts.idp.example/config.json 处)应具有以下 JSON 结构

json
{
  "accounts_endpoint": "/accounts.php",
  "client_metadata_endpoint": "/client_metadata.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 中向用户显示这些选项。

client_metadata_endpoint 可选

客户端元数据端点的 URL,该端点提供指向 RP 元数据和服务条款页面的 URL,用于 FedCM UI。

id_assertion_endpoint

ID 断言端点的 URL,该端点在发送有效的用户凭据时应响应一个验证令牌,RP 可以使用该令牌验证身份验证。

login_url

用户登录 IdP 的登录页面 URL。

branding 可选

包含品牌信息,这些信息将在浏览器提供的 FedCM UI 中使用,以根据 IdP 的需要自定义其外观。

下表总结了 FedCM API 发出的不同请求

端点/资源 方法 凭据化(使用 Cookie) 包含 Origin
well-known/config.json GET
accounts_endpoint GET
client_metadata_endpoint GET
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": "john_doe",
      "given_name": "John",
      "name": "John Doe",
      "email": "[email protected]",
      "picture": "https://idp.example/profile/123",
      "approved_clients": ["123", "456", "789"],
      "login_hints": ["john_doe", "[email protected]"]
    },
    {
      "id": "johnny",
      "given_name": "Johnny",
      "name": "Johnny",
      "email": "[email protected]",
      "picture": "https://idp.example/profile/456",
      "approved_clients": ["abc", "def", "ghi"],
      "login_hints": ["johnny", "[email protected]"]
    }
  ]
}

这包括以下信息

id

用户的唯一 ID。

name

用户的姓氏。

email

用户的电子邮件地址。

given_name 可选

用户的姓名。

picture 可选

用户头像图像的 URL。

approved_clients 可选

用户已注册的 RP 客户端数组。

login_hints 可选

表示帐户的字符串数组。这些字符串用于筛选浏览器为用户提供的登录帐户选项列表。当在相关 get() 调用中 identity.providers 中提供 loginHint 属性时,就会出现这种情况。任何在 login_hints 数组中包含与提供的 loginHint 匹配的字符串的帐户都将包含在内。

注意:如果用户未登录任何 IdP 帐户,则端点应响应 HTTP 401(未授权)

客户端元数据端点

浏览器通过 GET 方法向此端点发送非凭据化请求,并将 get() 调用中传递的 clientId 作为参数。

例如

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

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() 调用使用 mediation 选项值 "optional""silent" 发出时,就会出现这种情况。对于 IdP 来说,了解是否发生了自动重新身份验证对于性能评估和需要更高安全性时非常有用。例如,IdP 可以返回一个错误代码,告诉 RP 它需要显式的用户干预(mediation="required")。

注意: 如果 get() 调用成功,is_auto_selected 值也会通过 IdentityCredential.isAutoSelected 属性传递给 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() 调用将拒绝其承诺,并返回一个 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
    
  • 可以从 IdP 来源调用 Navigator.login.setStatus() 方法
    js
    /* Set logged-in status */
    navigator.login.setStatus("logged-in");
    
    /* Set logged-out status */
    navigator.login.setStatus("logged-out");
    

登录状态如何影响联合登录流程

RP 尝试联合登录 时,会检查登录状态

  • 如果登录状态为 "logged-in",则会向 IdP 的 帐户列表端点 发出请求,并且会在浏览器提供的 FedCM 对话框中向用户显示可用的登录帐户。
  • 如果登录状态为 "logged-out",则由 FedCM get() 请求返回的承诺将在不向帐户列表端点发出请求的情况下拒绝。在这种情况下,由开发人员负责处理流程,例如提示用户登录到合适的 IdP。
  • 如果登录状态为 "unknown",则会向 IdP 的帐户列表端点发出请求,并且登录状态将根据响应更新
    • 如果端点返回可用于登录的帐户列表,则将状态更新为 "logged-in",并在浏览器提供的 FedCM 对话框中向用户显示登录选项。
    • 如果端点不返回任何帐户,则将状态更新为 "logged-out";然后,由 FedCM get() 请求返回的承诺将拒绝。

如果浏览器和 IdP 登录状态不同步怎么办?

尽管登录状态 API 通知浏览器 IdP 的登录状态,但浏览器和 IdP 可能不同步。例如,IdP 会话可能会过期,这意味着所有用户帐户最终都会注销,但登录状态仍设置为 "logged-in"(应用程序无法将登录状态设置为 "logged-out")。在这种情况下,当尝试联合登录时,将向 IdP 的帐户列表端点发出请求,但不会返回任何可用帐户,因为会话不再可用。

发生这种情况时,浏览器可以通过在对话框中打开 IdP 的登录页面来动态地让用户登录 IdP(登录 URL 位于 IdP 的 配置文件 login_url 中)。此流程的确切性质取决于浏览器;例如,Chrome 这样处理

用户登录 IdP 后,IdP 应该

另请参阅