IdP 集成步骤
要与 FedCM 集成,IdP 需要执行以下操作:
- 提供一个 well-known 文件来识别 IdP。
- 提供一个配置文件和端点,用于帐户列表和断言颁发(以及可选的客户端元数据)。
- 使用登录状态 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 结构:
{
"provider_urls": ["https://accounts.idp.example/config.json"]
}
provider_urls
成员应包含一个 URL 数组,指向有效的 IdP 配置文件,RP 可以使用这些文件与 IdP 交互。数组长度目前限制为一。
Sec-Fetch-Dest
HTTP 标头
所有从浏览器通过 FedCM 发送的请求都包含
标头。所有接收凭证请求的 IdP 端点(即 Sec-Fetch-Dest
: webidentityaccounts_endpoint
和 id_assertion_endpoint
)都必须确认包含此标头,以防范 CSRF 攻击。
提供一个配置文件和端点
IdP 配置文件提供了浏览器处理身份联邦流程和管理登录所需的端点列表。这些端点需要与配置文件同源。
浏览器通过 GET
方法对配置文件发出非凭证请求,该请求不遵循重定向。这有效地阻止了 IdP 了解谁发出了请求以及哪个 RP 正在尝试连接。
配置文件(在我们的示例中托管在 https://accounts.idp.example/config.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 无关的。
例如
GET /accounts.php HTTP/1.1
Host: idp.example
Accept: application/json
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
成功请求的响应返回用户当前已登录的所有 IdP 帐户列表(不特定于任何特定的 RP),其 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"]
}
]
}
这包括以下信息,其中 name
、email
、username
和 tel
是可选的,但其中至少一个必须存在且非空。
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()
调用中。
例如
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 结构:
{
"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-urlencoded
的 Content-Type
,以及以下信息:
account_hint
-
一个字符串,指定 IdP 用于识别要断开连接的帐户的帐户提示。
client_id
-
一个字符串,指定 RP 的客户端标识符。
例如
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 服务器应:
-
使用 CORS(跨域资源共享)响应请求。
-
验证请求是否包含带有
webidentity
指令的Sec-Fetch-Dest
HTTP 标头。 -
将
Origin
标头与由client_id
确定的 RP 源匹配。如果不匹配,则拒绝 promise。 -
查找与
account_hint
匹配的帐户。 -
将用户帐户从 RP 连接帐户列表中断开连接。
-
以 JSON 格式响应已识别用户的
account_id
:json{ "account_id": "account456" }
注意:如果 IdP 希望断开与 RP 关联的所有帐户,它可以传递一个不匹配任何 account_id
的字符串,例如 "account_id": "*"
。
ID 断言端点
浏览器通过 POST
方法向此端点发送凭证请求,内容类型为 application/x-www-form-urlencoded
。请求还包含一个负载,其中包括有关尝试登录和要验证的帐户的详细信息。
它应该看起来像这样:
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 验证了用户,他们就可以登录用户、将他们注册到其服务等等。
{
"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-Origin
和 Access-Control-Allow-Credentials
标头,并且 Access-Control-Allow-Origin
必须包含请求者的源:
Access-Control-Allow-Origin: https://rp.example
Access-Control-Allow-Credentials: true
请注意,Access-Control-Allow-Origin
必须设置为请求者(RP)的特定源,并且不能是通配符值 *
。
如果没有这些标头,请求将因网络错误而失败。
ID 断言错误响应
如果 IdP 无法颁发令牌(例如,如果客户端未经授权),ID 断言端点将返回一个错误响应,其中包含有关错误性质的信息。例如:
{
"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 响应标头可以在顶级导航或同源子资源请求中设置:httpSet-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"
,则 FedCMget()
请求返回的 promise 将被拒绝,而不会向帐户列表端点发出请求。在这种情况下,由开发人员处理流程,例如提示用户去登录合适的 IdP。 - 如果 IdP 的登录状态为
"unknown"
,则会向帐户列表端点发出请求,并且登录状态会根据响应进行更新:- 如果端点返回可用于登录的帐户列表,则将状态更新为
"logged-in"
,并在浏览器提供的 FedCM 对话框中向用户显示登录选项。 - 如果端点不返回任何帐户,则将状态更新为
"logged-out"
;如果其他logged-in
IdP 不可用,FedCMget()
请求返回的 promise 将被拒绝。
- 如果端点返回可用于登录的帐户列表,则将状态更新为
如果浏览器和 IdP 登录状态不同步怎么办?
尽管登录状态 API 通知浏览器 IdP 登录状态,但浏览器和 IdP 有可能不同步。例如,IdP 会话可能会过期,这意味着所有用户帐户都已注销,但登录状态仍然设置为 "logged-in"
(应用程序无法将登录状态设置为 "logged-out"
)。在这种情况下,当尝试联合登录时,将向 IdP 的帐户列表端点发出请求,但由于会话不再可用,将不会返回任何可用帐户。
发生这种情况时,浏览器可以通过在对话框中打开 IdP 的登录页面来动态地让用户登录 IdP(登录 URL 在 IdP 的配置文件 login_url
中找到)。此流程的具体性质取决于浏览器;例如,Chrome 以这种方式处理它。
用户登录 IdP 后,IdP 应:
- 通过将登录状态设置为
"logged-in"
来通知浏览器用户已登录。 - 通过调用
IdentityProvider.close()
方法关闭登录对话框。
另见
- Federated Credential Management API on privacysandbox.google.com (2023)