category
了解如何使用 FedCM 进行可保护隐私的身份联合。
FedCM(联合凭据管理)是一种适用于联合身份服务(例如“使用以下账号登录”)的隐私保护方法;借助该方法,用户可以登录网站,而无需与身份服务或网站共享其个人信息。
如需详细了解 FedCM 用例、用户流和 API 路线图,请参阅 FedCM API 简介。
FedCM 开发环境
您需要在 Chrome 中的 IdP 和 RP 上都拥有安全上下文(HTTPS 或 localhost),才能使用 FedCM。
在 Android 版 Chrome 上调试代码
在本地设置并运行服务器以调试您的 FedCM 代码。您可以在使用具有端口转发功能的 USB 线连接的 Android 设备上,在 Chrome 中访问此服务器。
按照远程调试 Android 设备中的说明,您可以使用桌面设备上的开发者工具调试 Android 版 Chrome。
在 Chrome 上阻止第三方 Cookie
在实际强制执行 FedCM 之前,您可以先在 Chrome 上测试 FedCM 如何在不使用第三方 Cookie 的情况下工作。
如需阻止第三方 Cookie,请使用无痕模式,或者在 chrome://settings/cookies
或移动设备的桌面设备设置中选择“阻止第三方 Cookie”,或在移动设备上依次转到设置 > 网站设置 > Cookie。
使用 FedCM API
如需与 FedCM 集成,您可以为帐号列表、断言发布以及可选的客户端元数据创建知名文件、配置文件和端点。
FedCM 会公开 JavaScript API,RP 可使用这些 API 通过 IdP 登录。
创建一个众所周知的文件
为防止跟踪器滥用 API,必须通过 IdP 的 eTLD+1 的 /.well-known/web-identity
提供一个知名文件。
例如,如果 IdP 端点在 https://accounts.idp.example/
下提供,它们必须在 https://idp.example/.well-known/web-identity
中提供知名文件以及 IdP 配置文件。下面是一个知名文件内容示例:
{
"provider_urls": ["https://accounts.idp.example/config.json"]
}
JSON 文件必须包含 provider_urls
属性和一组 IdP 配置文件网址,这些网址可被 RP 在 navigator.credentials.get
中指定为 configURL
的路径部分。该数组中网址字符串的数量不得超过一个,但该数量可能会随着您的反馈而发生变化。
创建 IdP 配置文件和端点
IdP 配置文件提供了浏览器所需端点的列表。IdP 将托管此配置文件以及所需的端点和网址。所有 JSON 响应都必须使用 application/json
内容类型提供。
配置文件的网址由提供给在 RP 上执行的 navigator.credentials.get
调用提供的值确定。
const credential = await navigator.credentials.get({
identity: {
context: 'signup',
providers: [{
configURL: 'https://accounts.idp.example/config.json',
clientId: '********',
nonce: '******'
}]
}
});
const { token } = credential;
将 IdP 配置文件位置的完整网址指定为 configURL
。在 RP 上调用 navigator.credentials.get()
时,浏览器会使用不带 Origin
标头或 Referer
标头的 GET
请求来提取配置文件。该请求没有 Cookie,也不会跟踪重定向。 这样可有效防止 IdP 了解发出请求的人员以及哪个 RP 正在尝试连接。例如:
GET /config.json HTTP/1.1
Host: accounts.idp.example
Accept: application/json
Sec-Fetch-Dest: webidentity
浏览器期望 IdP 返回包含以下属性的 JSON 响应:
属性 | 说明 |
---|---|
accounts_ (必需) |
帐号端点的网址。 |
client_ (可选) |
客户端元数据端点的网址。 |
id_ (必需) |
ID 断言端点的网址。 |
disconnect (可选) |
断开连接端点的网址。 |
login_ (必需) |
用户用于登录 IdP 的登录页面网址。 |
branding (可选) |
包含各种品牌选项的对象。 |
branding. (可选) |
品牌选项,用于设置“继续为...”按钮的背景颜色。使用相关的 CSS 语法,即 hex-color 、hsl() 、rgb() 或 named-color 。 |
branding. (可选) |
品牌选项,用于设置“继续为...”按钮的文字颜色。使用相关的 CSS 语法,即 hex-color 、hsl() 、rgb() 或 named-color 。 |
branding. (可选) |
品牌选项,用于设置图标对象(显示在登录对话框中)。图标对象是一个包含两个参数的数组:
|
RP 可以通过 navigator.credentials.get()
的 identity.context
值修改 FedCM 对话框界面中的字符串,以适应预定义的身份验证上下文。可选属性可以是 "signin"
(默认)、"signup"
、"use"
或 "continue"
中的一个。
以下是 IdP 的响应正文示例:
{
"accounts_endpoint": "/accounts.php",
"client_metadata_endpoint": "/client_metadata.php",
"id_assertion_endpoint": "/assertion.php",
"disconnect_endpoint": "/disconnect.php",
"login_url": "/login",
"branding": {
"background_color": "green",
"color": "#FFEEAA",
"icons": [{
"url": "https://idp.example/icon.ico",
"size": 25
}]
}
}
浏览器提取配置文件后,会向 IdP 端点发送后续请求:
账号端点
IdP 的帐号端点会返回用户当前在 IdP 上登录的帐号列表。如果 IdP 支持多个帐号,此端点将返回所有已登录的帐号。
浏览器发送 GET
请求,其中包含带有 SameSite=None
的 Cookie,但不带 client_id
参数、Origin
标头或 Referer
标头。这样可以有效防止 IdP 了解用户尝试登录的 RP。例如:
GET /accounts.php HTTP/1.1
Host: accounts.idp.example
Accept: application/json
Cookie: 0x23223
Sec-Fetch-Dest: webidentity
收到请求后,服务器应执行以下操作:
- 验证请求是否包含
Sec-Fetch-Dest: webidentity
HTTP 标头。 - 将会话 Cookie 与已登录帐号的 ID 进行匹配。
- 返回帐号列表进行响应。
浏览器需要的 JSON 响应包括 accounts
属性以及一系列具有以下属性的帐号信息:
属性 | 说明 |
---|---|
id (必需) |
用户的唯一 ID。 |
name (必需) |
用户的姓氏和姓氏。 |
email (必需) |
用户的电子邮件地址。 |
given_ (可选) |
用户的名字。 |
picture (可选) |
用户头像图片的网址。 |
approved_ (可选) |
用户注册的 RP 客户端 ID 数组。 |
login_ (可选) |
IdP 支持指定帐号的所有可能过滤条件类型的数组。RP 可以使用 loginHint 属性调用 navigator. ,以选择性地显示指定的帐号。 |
domain_ (可选) |
一个数组,包含与该帐号相关联的所有网域。RP 可以使用 domainHint 属性调用 navigator. 来过滤帐号。 |
响应正文示例:
{
"accounts": [{
"id": "1234",
"given_name": "John",
"name": "John Doe",
"email": "john_doe@idp.example",
"picture": "https://idp.example/profile/123",
"approved_clients": ["123", "456", "789"],
"login_hints": ["demo1", "demo1@idp.example"]
}, {
"id": "5678",
"given_name": "Johnny",
"name": "Johnny",
"email": "johnny@idp.example",
"picture": "https://idp.example/profile/456",
"approved_clients": ["abc", "def", "ghi"],
"login_hints": ["demo2", "demo2@idp.example"],
"domain_hints": ["corp.example"]
}]
}
如果用户没有登录,则返回 HTTP 401 (Unauthorized)。
返回的帐号列表会被浏览器使用,并且不会提供给 RP。
客户端元数据端点
IdP 的客户端元数据端点返回依赖方的元数据,例如 RP 的隐私权政策和服务条款。RP 应提前向 IdP 提供其隐私权政策和服务条款的链接。如果用户尚未通过 IdP 在 RP 上注册,登录对话框中会显示这些链接。
浏览器使用不带 Cookie 的 client_id
navigator.credentials.get
发送 GET
请求。例如:
GET /client_metadata.php?client_id=1234 HTTP/1.1
Host: accounts.idp.example
Origin: https://rp.example/
Accept: application/json
Sec-Fetch-Dest: webidentity
收到请求后,服务器应执行以下操作:
- 确定
client_id
的 RP。 - 使用客户端元数据进行响应。
客户端元数据端点的属性包括:
属性 | 说明 |
---|---|
privacy_ (可选) |
RP 隐私权政策网址。 |
terms_ (可选) |
RP 服务条款网址。 |
浏览器期望端点返回 JSON 响应:
{
"privacy_policy_url": "https://rp.example/privacy_policy.html",
"terms_of_service_url": "https://rp.example/terms_of_service.html",
}
返回的客户端元数据被浏览器使用,并且不可供 RP 使用。
ID 断言端点
IdP 的 ID 断言端点会为已登录的用户返回断言。当用户使用 navigator.credentials.get()
调用登录 RP 网站时,浏览器会向此端点发送一个 POST
请求(其中包含具有 SameSite=None
的 Cookie,内容类型为 application/x-www-form-urlencoded
),其中包含以下信息:
属性 | 说明 |
---|---|
client_ (必需) |
RP 的客户端标识符。 |
account_ (必需) |
登录用户的唯一 ID。 |
nonce (可选) |
请求 Nonce,由 RP 提供。 |
disclosure_ |
返回 "true" 或 "false" 字符串(而不是布尔值)。如果未显示披露文字,则结果为 "false" 。如果 RP 的客户端 ID 包含在帐号端点的响应的 approved_ 属性列表中,或者浏览器在过去检测到注册时刻(缺少 approved_ ),就会发生这种情况。 |
is_ |
如果对 RP 执行了自动重新身份验证,is_ 表示 "true" 。否则为 "false" 。这有助于支持更多与安全相关的功能。例如,有些用户可能更喜欢更高的安全等级,这就要求在身份验证时明确指定用户中介。如果 IdP 在未使用此类中介功能的情况下收到令牌请求,他们可能会以不同的方式处理该请求。例如,返回错误代码,以便 RP 可以通过 mediation: required 再次调用 FedCM API。 |
HTTP 标头示例:
POST /assertion.php HTTP/1.1
Host: accounts.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
收到请求后,服务器应执行以下操作:
- 使用 CORS(跨域资源共享)响应请求。
- 验证请求是否包含
Sec-Fetch-Dest: webidentity
HTTP 标头。 - 将
Origin
标头与由client_id
确定的 RP 源进行匹配。如果不匹配,则拒绝。 - 将
account_id
与已登录帐号的 ID 进行匹配。如果不匹配,则拒绝。 - 使用令牌进行响应。如果请求被拒绝,则使用错误响应进行响应。
令牌的颁发方式由 IdP 决定,但一般而言,它会使用帐号 ID、客户端 ID、颁发者来源、nonce
等信息进行签名,以便 RP 可以验证令牌的真实性。
浏览器需要包含以下属性的 JSON 响应:
属性 | 说明 |
---|---|
token (必需) |
令牌是一个字符串,其中包含有关身份验证的声明。 |
{
"token": "***********"
}
返回的令牌由浏览器传递给 RP,以便 RP 可以验证身份验证。
返回错误响应
id_assertion_endpoint
还可以返回“错误”响应,其中包含两个可选字段:
code
:IdP 可以从 OAuth 2.0 指定的错误列表中选择一个已知错误(invalid_request
、unauthorized_client
、access_denied
、server_error
和temporarily_unavailable
),也可以使用任意字符串。如果是后者,Chrome 会呈现错误界面并显示一般错误消息,并将代码传递给 RP。url
:用于识别包含错误相关信息的人类可读网页,以便向用户提供与错误有关的其他信息。此字段对用户非常有用,因为浏览器无法在原生界面中提供丰富的错误消息。例如,后续步骤链接、客户服务联系信息等。如果用户想要详细了解错误详情及修正方法,可以从浏览器界面访问所提供的页面,以查看详情。该网址必须与 IdPconfigURL
属于同一网站。
// id_assertion_endpoint response
{
"error" : {
"code": "access_denied",
"url" : "https://idp.example/error?type=access_denied"
}
}
断开端点连接
通过调用 IdentityCredential.disconnect()
,浏览器会向此断开连接端点发送一个包含 Cookie SameSite=None
、内容类型为 application/x-www-form-urlencoded
的跨源 POST
请求,其中包含以下信息:
属性 | 说明 |
---|---|
account_ |
IdP 账号的提示。 |
client_ |
RP 的客户端标识符。 |
POST /disconnect.php 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
收到请求后,服务器应执行以下操作:
- 使用 CORS(跨域资源共享)响应请求。
- 验证请求是否包含
Sec-Fetch-Dest: webidentity
HTTP 标头。 - 将
Origin
标头与由client_id
确定的 RP 源进行匹配。如果不匹配,则拒绝。 - 将
account_hint
与已登录的账号的 ID 进行匹配。 - 断开用户帐号与 RP 的连接。
- 以 JSON 格式使用已识别的用户帐号信息响应浏览器。
响应 JSON 载荷示例如下所示:
{
"account_id": "account456"
}
相反,如果 IdP 希望浏览器断开与 RP 关联的所有帐号的关联,请传递一个与任何帐号 ID 都不匹配的字符串,例如 "*"
。
登录网址
借助 Login status API,IdP 必须将用户的登录状态告知浏览器。但是,状态可能不同步,例如当会话过期时。在这种情况下,浏览器可以动态地让用户通过 IdP 配置文件的 login_url
指定的登录页面网址登录 IdP。
FedCM 对话框会显示一条建议登录的消息,如下图所示。
当用户点击 Continue 按钮时,浏览器会打开一个显示 IdP 登录页面的弹出式窗口。
该对话框是一个包含第一方 Cookie 的常规浏览器窗口。对话框中的操作由 IdP 决定,没有窗口句柄可以向 RP 页面发出跨源通信请求。用户登录后,IdP 应执行以下操作:
- 发送
Set-Login: logged-in
标头或调用navigator.login.setStatus("logged-in")
API 以通知浏览器用户已登录。 - 调用
IdentityProvider.close()
以关闭对话框。
将用户在身份提供方处的登录状态告知浏览器
Login status API 是一种机制,在这种机制中,网站(尤其是 IdP)会向浏览器通知用户在 IdP 上的登录状态。借助此 API,浏览器可以减少向 IdP 发送的不必要请求并缓解潜在的计时攻击。
当用户在 IdP 上登录或退出其所有 IdP 帐号时,IdP 可以通过以下两种方式向浏览器表明用户的登录状态:发送 HTTP 标头或调用 JavaScript API。对于每个 IdP(由其配置网址标识),浏览器都会保留一个表示登录状态的三态变量,该变量可能具有 logged-in
、logged-out
和 unknown
值。默认状态为 unknown
。
如需表明用户已登录,请在顶级导航栏中发送 Set-Login: logged-in
HTTP 标头,或在 IdP 源站发送同网站子资源请求:
Set-Login: logged-in
或者,在顶级导航栏中从 IdP 来源调用 JavaScript API navigator.login.setStatus("logged-in")
:
navigator.login.setStatus("logged-in")
这些调用会将用户的登录状态记录为 logged-in
。当用户的登录状态设置为 logged-in
时,调用 FedCM 的 RP 会向 IdP 的帐号端点发出请求,并在 FedCM 对话框中向用户显示可用帐号。
如需表明用户已退出其所有帐号,请在顶级导航栏中发送 Set-Login: logged-out
HTTP 标头,或在 IdP 来源处发送同网站子资源请求:
Set-Login: logged-out
或者,在顶级导航栏中从 IdP 来源调用 JavaScript API navigator.login.setStatus("logged-out")
:
navigator.login.setStatus("logged-out")
这些调用会将用户的登录状态记录为 logged-out
。当用户的登录状态为 logged-out
时,如果不向 IdP 的帐号端点发出请求,调用 FedCM 会静默失败。
unknown
状态是在 IdP 使用 LoginStatus API 发送信号之前设置的。引入了 Unknown
以改进过渡,因为在此 API 发布时,用户可能已经登录 IdP。IdP 可能没有机会在首次调用 FedCM 时向浏览器发出信号。在这种情况下,Chrome 会向 IdP 的帐号端点发出请求,并根据来自帐号端点的响应更新状态:
- 如果端点返回活跃帐号列表,请将状态更新为
logged-in
,并打开 FedCM 对话框以显示这些帐号。 - 如果端点未返回任何帐号,请将状态更新为
logged-out
并失败 FedCM 调用。
让用户通过动态登录流程登录
即使 IdP 不断将用户的登录状态告知浏览器,它也可能不同步,例如当会话过期时。当登录状态为 logged-in
时,浏览器会尝试向帐号端点发送基于凭据的请求,但由于会话不再可用,服务器不会返回任何帐号。在这种情况下,浏览器可以动态让用户通过弹出式窗口登录 IdP。
登录到身份提供方的依赖方
IdP 的配置和端点可用后,RP 可以调用 navigator.credentials.get()
来请求允许用户使用 IdP 登录 RP。
在调用该 API 之前,您需要确认 [FedCM 在用户的浏览器中可用]。如需检查 FedCM 是否可用,请将此代码封装在 FedCM 实现中:
if ('IdentityCredential' in window) {
// If the feature is available, take action
}
如需请求允许用户从 RP 登录 IdP,请执行以下操作,例如:
const credential = await navigator.credentials.get({
identity: {
providers: [{
configURL: 'https://accounts.idp.example/config.json',
clientId: '********',
nonce: '******'
}]
}
});
const { token } = credential;
providers
属性接受具有以下属性的 IdentityProvider
对象数组:
属性 | 说明 |
---|---|
configURL (必需) |
IdP 配置文件的完整路径。 |
clientId (必需) |
由 IdP 签发的 RP 的客户端标识符。 |
nonce (可选) |
一个随机字符串,用于确保系统针对此特定请求发出响应。防止重放攻击。 |
loginHint (可选) |
通过指定帐号端点提供的某个 login_ 值,FedCM 对话框会选择性地显示指定的帐号。 |
domainHint (可选) |
通过指定帐号端点提供的某个 domain_ 值,FedCM 对话框会选择性地显示指定的帐号。 |
浏览器处理注册和登录用例的方式有所不同,具体取决于帐号列表端点响应中是否存在 approved_clients
。如果用户已经注册 RP,浏览器不会显示披露文字“To continue with ....”。
注册状态取决于是否满足以下条件:
- 如果
approved_clients
包含 RP 的clientId
, - 如果浏览器记住用户已注册 RP。
- 登录 发表评论