1. 引言
本节为非规范性内容。
随着 Web 的发展,隐私保护相关的变革不断进行(如 Safari、Firefox、 Chrome),以及底层隐私原则的变化(例如 隐私模型)。
随着这些变革,Web 平台的基本假设正在被重新定义或移除。第三方上下文访问 Cookie 就是这些假设之一。虽然整体对 Web 有益,但第三方 Cookie 的废弃移除了某些联合身份设计所依赖的基础构件。
联合凭据管理 API 旨在为那些依赖第三方 Cookie 的联合身份设计提供桥梁。该 API 提供了支持联合身份所需的原语,包括登录、登出和撤销等环节,即使其依赖第三方 Cookie。
为了在不使用第三方 Cookie 的情况下提供联合身份原语,API 将 用户代理作为 RP(请求用户信息以联合登录的网站)与 IDP(提供用户信息以联合登录的网站)之间的中介。这个中介在允许RP和IDP知道其与用户的关联前需要用户授权。
该规范主要依赖于 用户代理和 IDP 的变更,尽量减少对 RP 的要求。FedCM API 提供了一种认证和获取令牌的方式。
< html > < head > < title > 欢迎来到我的网站</ title > </ head > < body > < button onclick = "login()" > 使用 idp.example 登录</ button > < script > let nonce; async function login() { try { // 假设有一个方法返回随机数,将值存入变量,稍后可用于校验令牌中的值。 nonce= random(); // 提示用户从 IDP 选择一个账户,供 RP 进行联合登录。如果成功,Promise // 返回 IdentityCredential 对象,可从中提取 |token|。 // 这是从 IDP 到 RP 的不透明字符串。 let token= await navigator. credentials. get({ identity: { providers: [{ configURL: "https://idp.example/manifest.json" , clientId: "123" , nonce: nonce, }] } }); } catch ( e) { // FedCM 调用失败。 } } </ script > </ body > </ html >
总体来看,联合凭据管理 API 通过协作的 IDP 和 RP 实现中介。
§ 3 身份提供者 HTTP API 定义了一组 IDP 需公开的 HTTP API,以及可用的 § 2 浏览器 API 入口。
用户代理在中介过程中保证该 API 不易被用于追踪用途,同时保留了身份联合的功能。
2. 浏览器 API
浏览器 API 向 RP 和 IDP 提供可调用的接口,并在用户身份交换过程中进行中介。
注册/登录 API 被 RP 用于请求浏览器中介与 IDP 的关系,并获取令牌。
注意: RP 不区分注册和登录,都会用同一个 API 调用。
如果一切顺利,依赖方将获得一个 IdentityCredential
对象,里面包含可用于用户认证的令牌。
const credential= await navigator. credentials. get({ identity: { providers: [{ configURL: "https://idp.example/manifest.json" , clientId: "123" , }] } });
对于带 Cookie 的请求,将包含未分区 Cookie,就像同源请求一样,无论 SameSite 属性为何(该属性用于第三方加载资源,而不是第一方)。这样,IDP 可以轻松采用 FedCM API。由于 RP 无法以任何方式检查获取结果,因此这不会带来安全问题。
2.1. 登录状态 API
2.1.1. 登录状态映射
每个 用户代理
都维护一个全局、持久性的 登录状态映射,这是一个初始为空的 映射。该映射的 键 是 origin(即 IDP 的
origin),而 值 是枚举类型,可以是 "unknown
"、"logged-in
" 和 "logged-out
"。
-
断言 value 必须是 logged-in 或 logged-out 之一。
2.1.2. 基础架构算法
true
时,视为
与其祖先同站点:
2.1.3. HTTP 头 API
IDP 可以通过 HTTP 响应 头部来设置登录状态,如下所示。
HTTP 头检查应移入 Fetch 规范,因为它影响所有资源加载。
对于每个 http-redirect fetch 和 http fetch
的 响应,令 value 为用名称 "Set-Login
" 和类型 "item
"
从响应头列表中 获取结构化字段值 的结果。如果 value 非空,则按如下处理该头:
-
如果请求的 destination 不是
"document"
: -
断言 value 是元组。
-
令 token 为 value 的第一个元素。
-
如果 token 为
"logged-out"
,则为 origin 设置登录状态为 logged-out。
2.1.4. JavaScript API
IDP 也可以使用 JavaScript API 来更新已存储的登录状态:
enum {
LoginStatus ,
"logged-in" , }; [
"logged-out" Exposed =Window ,SecureContext ]interface {
NavigatorLogin Promise <undefined >(
setStatus LoginStatus ); };
status partial interface Navigator { [SecureContext ]readonly attribute NavigatorLogin ; };
login
setStatus()
并传入参数 status 时:
-
如果 当前设置对象不是 与其祖先同站点,则抛出
SecurityError
DOMException
。 -
令 value 为 logged-in(如果 status 为
"logged-in"
),或 logged-out(如果 status 为"logged-out"
)。 -
为 origin 设置登录状态为 value。
2.1.5. 清除登录状态映射数据
当以下情况发生时,用户代理也必须清除 登录状态映射 数据:
- 用户清除所有 Cookie 或站点设置数据
-
用户代理必须清除整个映射。
- 用户为某个特定 origin 清除所有 Cookie 或所有站点数据
-
用户代理必须移除所有受删除的 Cookie 影响的条目,也就是任何键为删除 Cookie 可被发送到的 origin 的条目。
注意: 例如,域 Cookie 可能影响被删除 origin 的子域,比如清除
google.com
的 Cookie 也应重置accounts.google.com
的登录状态,因为它可能依赖于 google.com 的域 Cookie。 - 用户删除单个 Cookie(如果用户代理允许)
-
行为由用户代理自行定义。
注意: 用户代理可以选择将状态重置为 unknown,因为无法确定此 Cookie 是否影响授权状态。
- 用户代理收到 Clear-Site-Data 头,并且值为
"cookies"
或"*"
,且 请求的 client 不为 null,并且 client 的 origin 与 顶层 origin 是 同源 -
在 清除某 origin 的 Cookie 时,必须移除 登录状态映射 中键为输入的 origin 的条目。
一旦 Clear-Site-Data 支持分区 Cookie,此处表述应更新。
注意: 其他网站发起的 Cookie 更改不应影响此映射。IDP 登录状态变更时,应显式发送 Set-Login 头。RP 的状态不应影响此映射,因为它只反映 IDP 的状态。
2.2. 已连接账户集合
每个 用户代理 都有一个全局 已连接账户集合,初始为空的 有序集合。其 项 是形如 (rp, idp, account) 的三元组,其中 rp 是 RP 的 origin,idp 是 IDP 的 origin,account 是代表账户标识符的字符串。它表示用户曾用 FedCM 通过该 idp 的 account 登录到 rp 的三元组集合。
已连接账户集合 应该为 RP 双键(即应包含请求方和嵌入方,或换言之 iframe 和顶层页面)。否则顶层状态可被嵌入方使用和修改,带来信息泄漏和跨域通信问题。
如果用户为某个 origin 清除浏览数据(如 Cookie、localStorage 等),用户代理必须 移除 已连接账户集合中任意包含该 origin 的三元组。
IdentityProviderConfig
provider、IdentityProviderAccount
account 和 globalObject,执行以下步骤。它返回 (rp, idp, account) 三元组。
IdentityProviderAccount
account 是否 有资格自动重新认证,给定 IdentityProviderConfig
provider 和 globalObject,执行以下步骤,返回布尔值。
-
如果 account 包含
approved_clients
,且其approved_clients
不 包含 provider 的clientId
,返回 false。 -
令 triple 为用 计算已连接账户键得到的结果,输入为 provider、account 和 globalObject。
IdentityProviderAccount
account、IdentityProviderConfig
provider 和 globalObject,执行以下步骤。返回 connected 或 disconnected。
-
如果 account 包含
approved_clients
:-
如果 account 的
approved_clients
包含 provider 的clientId
,返回 connected。 -
返回 disconnected。
-
-
令 triple 为用 计算已连接账户键得到的结果,输入为 provider、account 和 globalObject。
-
返回 disconnected。
IdentityProviderConfig
provider、IdentityProviderAccount
account 和 globalObject(即 RP
的),执行以下步骤:
2.3. IdentityCredential 接口
本规范引入了一种新的 Credential
类型,称为 IdentityCredential
:
dictionary :
IdentityCredentialDisconnectOptions IdentityProviderConfig {required USVString ; }; [
accountHint Exposed =Window ,SecureContext ]interface :
IdentityCredential Credential {static Promise <undefined >(
disconnect optional IdentityCredentialDisconnectOptions = {});
options readonly attribute USVString ?;
token readonly attribute boolean ; };
isAutoSelected
id
-
id
的属性 getter 返回空字符串。 token
isAutoSelected
-
isAutoSelected
的属性 getter 返回其设置值。表示在经过 UI 流程后,此IdentityCredential
是否由用户自动选择。 [[type]]
-
IdentityCredential
的[[type]]
的值为 "identity"。 [[discovery]]
本规范的主要入口点是由 Credential Management API 提供的入口。
2.3.1. disconnect 方法
disconnect
方法,并传入 IdentityCredentialDisconnectOptions
options 时,执行如下步骤:
-
令 globalObject 为 当前全局对象。
-
令 document 为 globalObject 的 关联文档。
-
如果 document 不被允许使用 allowed to use 的 identity-credentials-get 策略控制特性,则抛出 "
NotAllowedError
"DOMException
。 -
令 promise 为新建的
Promise
。 -
返回 promise。
IdentityCredentialDisconnectOptions
options、Promise
promise 和 globalObject 时,执行如下步骤:
-
断言:这些步骤在 并行运行。
-
令 configUrl 为使用 options 的
configURL
和 globalObject 执行 parse url 的结果。 -
如果 configUrl 失败,则用 "
InvalidStateError
"DOMException
拒绝 promise。 -
用 内容安全策略第 3 级 里的 connect-src 指令检查传入的 configUrl。如果失败,则用 "
NetworkError
"DOMException
拒绝 promise。 -
如果此 globalObject 存在其他挂起的
disconnect
调用(如尚未抛出异常或其Promise
尚未解决),则用 "NetworkError
"DOMException
拒绝 promise。 -
如果 configUrl 不是 潜在可信 origin,则用 "
NetworkError
"DOMException
拒绝 promise。 -
如果用户已在 globalObject 上禁用 FedCM API,则用 "
NetworkError
"DOMException
拒绝 promise。 -
如果不存在某个 account,使得 已连接账户集合 包含用 计算已连接账户键(输入为 account、options 和 globalObject)获得的结果,则用 "
NetworkError
"DOMException
拒绝 promise。此检查可以遍历 已连接账户集合或用单独的数据结构加速查找。 -
令 config 为用 获取配置文件、provider 和 globalObject 得到的结果。
-
如果 config 失败,则用 "
NetworkError
"DOMException
拒绝 promise。 -
令 disconnectUrl 为用 计算 manifest URL,输入为 provider、config.
disconnect_endpoint
和 globalObject。 -
如果 disconnectUrl 失败,则用 "
NetworkError
"DOMException
拒绝 promise。 -
发送断开连接请求,输入 disconnectUrl、options 和 globalObject,令 result 为结果。
-
令 idpOrigin 为 configUrl 对应的 origin。
-
如果 result 失败:
-
移除所有连接,传入 rpOrigin 和 idpOrigin。
-
拒绝 promise,错误为 "
NetworkError
"DOMException
。 -
返回。
-
-
令 accountId 为 result(注意不是失败)。
-
移除连接,输入 accountId、rpOrigin 和 idpOrigin,结果赋值给 wasAccountRemoved。
-
如果 wasAccountRemoved 为 false,则用 移除所有连接,传入 rpOrigin 和 idpOrigin。
-
解决 promise。
2.3.1.1. 断开连接请求
发送断开连接请求算法会向之前用于联合登录的账户发送断开请求,目标是 RP。
IdentityCredentialDisconnectOptions
options 和 globalObject,执行如下步骤。本步骤返回 USVString
或失败。
-
令 requestBody 为用如下列表执行 urlencoded serializer 的结果:
-
("client_id", options 的
clientId
) -
("account_hint", options 的
accountHint
)
-
-
令 request 为如下新建的 请求:
- url
-
disconnectUrl
- method
-
"POST"
- body
-
对 requestBody 执行 UTF-8 编码
- redirect mode
-
"error"
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
- header list
-
包含一个 header 的 列表,name 为
Accept
,value 为application/x-www-form-urlencoded
- credentials mode
-
"include"
- mode
-
"cors"
-
令 accountId 为 null。
-
发起请求,输入 request 和 globalObject,并设置 processResponseConsumeBody 为以下步骤,输入为 response response 和 responseBody:
-
令 json 为从 response 和 responseBody 执行 提取 JSON fetch 响应 的结果。
-
转换 json 为
DisconnectedAccount
,赋值给 account。 -
如果前两步有异常,则设 accountId 为失败并返回。
-
设 accountId 为 account 的
account_id
。
-
-
等待 accountId 设置完成。
-
返回 accountId。
dictionary {
DisconnectedAccount required USVString account_id ; };
2.3.2. CredentialRequestOptions
本节定义了传入 JavaScript 调用的字典:
const credential= await navigator. credentials. get({ identity: { // IdentityCredentialRequestOptions providers: [{ // sequence<IdentityCredentialRequestOptions> configURL: "https://idp.example/manifest.json" , // IdentityProviderConfig.configURL clientId: "123" , // IdentityProviderConfig.clientId nonce: "nonce" // IdentityProviderConfig.nonce }] } });
本规范扩展了 CredentialRequestOptions
对象:
partial dictionary CredentialRequestOptions {IdentityCredentialRequestOptions ; };
identity
IdentityCredentialRequestOptions
包含 IdentityProviderConfig
的列表,表示 RP 支持并已预注册的身份提供方(即 IDP 已给出 RP
一个 clientId
)。
IdentityCredentialRequestOptions
还包含一个 IdentityCredentialRequestOptionsContext
,用户代理可据此为用户提供更有意义的对话框。
enum {
IdentityCredentialRequestOptionsContext ,
"signin" ,
"signup" ,
"use" };
"continue" dictionary {
IdentityCredentialRequestOptions required sequence <IdentityProviderRequestOptions >;
providers IdentityCredentialRequestOptionsContext = "signin"; };
context
每个 IdentityProviderConfig
代表一个 IDP,即 RP 支持的身份提供方(例如有预注册协议)。
dictionary {
IdentityProviderConfig required USVString ;
configURL required USVString ; };
clientId dictionary :
IdentityProviderRequestOptions IdentityProviderConfig {USVString ;
nonce DOMString ;
loginHint DOMString ; };
domainHint
configURL
-
身份提供方配置文件的 URL。
clientId
nonce
loginHint
-
表示账户登录提示的字符串,RP 希望用户代理展示给用户。如果提供,则不展示不匹配该提示的账户。通常对应期望
IdentityProviderAccount
的某个属性。 domainHint
-
表示 RP 感兴趣的域提示的字符串,或者 "any"(RP 只要至少一个域提示关联的账户)。如果提供,则不展示不匹配该域提示的账户。
2.3.3.
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
内部方法
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
算法在 凭据管理
1 § 2.5.1 请求凭据 中并行运行,用于请求凭据并返回 IdentityCredential
或错误。
该 内部方法接受三个参数:
origin
-
此参数为调用方 相关设置对象的 origin,由
get()
的实现决定,即CredentialsContainer
的 请求Credential
抽象操作。 options
-
此参数是一个
CredentialRequestOptions
对象,其中identity
成员存在。 sameOriginWithAncestors
-
此参数为布尔值,仅当调用方的 环境设置对象是 与其祖先同源时为
true
。跨源调用时为false
。注意: 调用该 内部方法 表示通过了 权限策略,该策略在 Credential Management Level 1 层评估。详见 § 4 权限策略集成。因此,sameOriginWithAncestors 未被使用。
options.signal
用作请求的中止信号。
IdentityCredential
的
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
算法时,用户代理必须执行如下步骤。本算法返回 IdentityCredential
或向调用方抛出错误。
-
断言:这些步骤在 并行运行。
-
如果 options["
identity
"]["providers
"] 的 长度不等于 1,则在 DOM 操作任务源上,为 globalObject 排队任务抛出新的 "NetworkError
"DOMException
注意: globalObject 目前未传递到
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
算法。见 相关 issue。 -
用户代理可自主决定何时继续后续步骤。
注意: 例如,用户代理可在发起网络请求前展示身份提供方选择器,或在地址栏提供按钮并等待用户操作。
-
令 credential 为用 创建 IdentityCredential 算法,输入 provider、options 和 globalObject 得到的结果。
-
如果 credential 是一个二元组:
-
令 throwImmediately 为二元组第二个元素的值。
-
若以下条件全部满足,用户代理应在下一步之前随机延迟:
-
throwImmediately 为 false
-
Promise 拒绝延迟未被 用户代理自动化禁用
-
用户代理未实现其他方式防止 RP 得知用户是否登录过 RP
注意: 如 Promise 立即被解决,RP 可能推测没有弹窗,Promise 很快(仅网络延迟后)被解决。弹窗表明用户在 IDP 有账户。为防止信息泄露,尤其无用户确认时,需延迟。不同 UA 可通过不同 UI 方式防止泄漏。
-
-
在 DOM 操作任务源上排队任务,抛出新的 "
NetworkError
"DOMException
。
-
-
否则,返回 credential。
2.3.4. 创建 IdentityCredential
创建
IdentityCredential 算法会发起各种 FedCM 请求、展示用户代理 UI,并创建 IdentityCredential
,然后返回给 RP。
IdentityProviderRequestOptions
provider、CredentialRequestOptions
options 和 globalObject,执行如下步骤。返回 IdentityCredential
或二元组 (failure, bool),其中 bool 表示是否跳过异常延迟抛出。
-
断言:这些步骤在 并行运行。
-
令 loginStatus 为用 获取登录状态算法,输入 provider 的
configURL
的 origin 得到的结果。 -
如果 loginStatus 是 unknown,用户代理可以将其设为 logged-out。
-
如果 loginStatus 是 logged-out,用户代理必须执行下列之一:
-
返回 (failure, false)。
-
提示用户是否继续,若用户继续,用户代理应将 loginStatus 设为 unknown。此提示可包含 显示 IDP 登录对话框的操作。
-
若用户取消该对话框,返回 (failure, true)。
-
若用户触发对话框:
-
令 config 为用 获取配置文件,输入 provider 和 globalObject 得到的结果。
-
若 config 失败,返回 (failure, true)。
-
用 显示 IDP 登录对话框,输入 config 和 provider。
-
如算法返回失败,返回 (failure, true)。
-
-
-
-
令 requiresUserMediation 为 provider 的
configURL
的 origin 的 requires user mediation。 -
令 mediation 为 options 的
mediation
。 -
若 requiresUserMediation 为 true 且 mediation 为 "
silent
",返回 (failure, true)。 -
令 config 为用 获取配置文件,输入 provider 和 globalObject 得到的结果。
-
若 config 失败,返回 (failure, false)。
-
获取账户步骤:令 accountsList 为用 获取账户,输入 config、provider 和 globalObject 得到的结果。
-
若 accountsList 失败或长度为 0:
-
为
configURL
的 origin 设置登录状态为 logged-out。UA 可选择在未向服务器发送任何凭据时跳过此步骤。注意: 例如如果因 DNS 错误导致 fetch 失败,则未发送凭据,IDP 不会获知用户身份。因此不确定用户是否已登录,可能不需重置状态。
-
不匹配对话框步骤:如 loginStatus 为 logged-in,向用户展示对话框。具体内容由 UA 定义。该对话框应允许用户触发 显示 IDP 登录对话框算法,输入 config 和 provider。此对话框即 确认 IDP 登录对话框。
注意: 此情况为浏览器预期用户已登录,但账户获取结果表明用户已登出。
注意: 此对话框确保无法静默追踪用户,只要向服务器发送了凭据就会有 UI 展现。
-
等待以下之一发生:
-
用户关闭对话框,返回 (failure, true)。
-
触发了 显示 IDP 登录对话框 算法:
-
令 result 为该算法结果。
-
若 result 失败,返回 (failure, true)。UA 可在失败前后向用户展示提示。
-
否则,返回 获取账户步骤。
-
-
-
-
-
断言:accountsList 未失败且长度不为 0。
-
如果 provider 的
loginHint
非空:-
对 accountList 每个 account,如 account 的
login_hints
不 包含 provider 的loginHint
,则移除该 account。 -
如 accountList 现为空,则进入 不匹配对话框步骤。
-
-
如果 provider 的
domainHint
非空:-
对 accountList 每个 account:
-
如果
domainHint
为 "any":-
如果 account 的
domain_hints
为空,则移除该 account。
-
-
否则,如 account 的
domain_hints
不 包含 provider 的domainHint
,则移除该 account。
-
-
如 accountList 现为空,则进入 不匹配对话框步骤。
-
-
对 accountsList 的每个 acc:
注意: 用户代理可以选择在初始 UI 展示时不立即获取头像,必要时再获取。头像获取失败可忽略,顺序不限。
-
令 registeredAccount、numRegisteredAccounts 分别为 null 和 0。
-
令 account 为 null。
-
对 accountsList 的每个 acc:
-
如 acc 用 自动重新认证资格判断(输入 provider 和 globalObject)为 true,则 registeredAccount 设为 acc,numRegisteredAccounts 加 1。
-
-
令 permission、disclosureTextShown、isAutoSelected 均为 false。
-
如 mediation 非 "
required
" 且 requiresUserMediation 为 false 且 numRegisteredAccounts 等于 1:-
设 account 为 registeredAccount,permission 为 true。UA 可向用户展示自动重新认证的 UI。
-
设 isAutoSelected 为 true。
-
-
否则,如 mediation 为 "
silent
",返回 (failure, true)。 -
否则,如 accountsList 长度为 1:
-
否则:
-
等待 用户代理弹窗关闭(如有),即请求用户选择或授权的弹窗。
-
断言:account 非 null。
-
如 permission 为 false,则返回 (failure, true)。
-
令 credential 为 获取身份断言算法(输入 account 的
id
、disclosureTextShown、isAutoSelected、provider、config、globalObject)。 -
返回 credential。
2.3.5. 获取配置文件
获取配置文件算法会从IDP拉取Well-Known文件和配置文件,检查配置文件是否在Well-Known文件中声明,并返回该配置。
IdentityProviderRequestOptions
provider和globalObject,执行如下步骤。返回IdentityProviderAPIConfig
或失败。
-
如果configUrl失败,则返回失败。
-
对传入的configUrl执行内容安全策略3级检查,使用connect-src指令。如果失败,则返回失败。
-
如果configUrl不是潜在可信URL,则返回失败。
-
令rootUrl为新建的URL。
-
令config、configInWellKnown均为null。
-
如果rpOrigin不是不透明origin, 且rootUrl的host等于rpOrigin的可注册域名, 且rootUrl的scheme等于rpOrigin的scheme, 则设configInWellKnown为true。
注意:由于域Cookie在整个站点都有效,如果RP与IDP同属一个站点,则无需执行Well-Known检查,不会带来额外隐私收益。
-
否则:
-
令wellKnownRequest为如下新建的请求:
- URL
-
rootUrl
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
-
一个唯一的不透明origin
- header list
- referrer policy
-
"no-referrer"
- credentials mode
-
"omit"
- mode
-
"no-cors"
-
发起请求,参数为wellKnownRequest和globalObject,并设置processResponseConsumeBody如下,输入为response response和responseBody:
-
令json为用response和responseBody执行extract the JSON fetch response的结果。
-
转换 json为
IdentityProviderWellKnown
,赋值给discovery。 -
如前两步有异常,或discovery["
provider_urls
"]长度大于1,则设configInWellKnown为false。关于
provider_urls
数组长度限制可见讨论。 -
否则,如discovery["
provider_urls
"][0]等于provider的configURL
,则设configInWellKnown为true,否则为false。
-
-
-
令configRequest为如下新建的请求:
- url
-
configUrl
- redirect mode
-
"error"
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
-
一个唯一的不透明origin
- header list
- referrer policy
-
"no-referrer"
- credentials mode
-
"omit"
- mode
-
"no-cors"
-
发起请求,参数为configRequest和globalObject,并设置processResponseConsumeBody如下,输入为response response和responseBody:
-
令json为用response和responseBody执行extract the JSON fetch response的结果。
-
转换json为
IdentityProviderAPIConfig
,赋值给config。 -
如前两步有异常,则设config为失败。
-
设config的
login_url
为用provider、config和globalObject执行computing the manifest URL的结果。 -
如果config的
login_url
为null,则返回失败。
-
-
等待config和configInWellKnown都设置完成。
-
如configInWellKnown为true,则返回config。否则返回失败。
注意:采用两层文件系统是为了防止IDP通过配置文件路径编码信息,轻易判断用户访问的RP。要求Well-Known文件必须放在IDP根目录。配置文件路径可任意,但用户代理找不到Well-Known文件声明则不会用该配置文件。这样可避免通过RP路径实现指纹识别(例如路径包含RP信息)。详见 § 7.3.1 Manifest 指纹识别。
dictionary {
IdentityProviderWellKnown required sequence <USVString >provider_urls ; };dictionary {
IdentityProviderIcon required USVString url ;unsigned long size ; };dictionary {
IdentityProviderBranding USVString background_color ;USVString color ;sequence <IdentityProviderIcon >icons ;USVString name ; };dictionary {
IdentityProviderAPIConfig required USVString accounts_endpoint ;required USVString client_metadata_endpoint ;required USVString id_assertion_endpoint ;required USVString ;
login_url USVString disconnect_endpoint ;IdentityProviderBranding branding ; };
2.3.6. 获取账户
获取账户算法通过accounts endpoint获取用户已登录的IDP账户列表,以便用户代理后续展示FedCM界面。
IdentityProviderAPIConfig
config、IdentityProviderRequestOptions
provider和globalObject,执行如下步骤。返回IdentityProviderAccountList
。
-
令accountsUrl为用provider、config["
accounts_endpoint
"]和globalObject执行computing the manifest URL的结果。 -
如果accountsUrl失败,则返回空列表。
-
令request为如下新建的请求:
- url
-
accountsUrl
- redirect mode
-
"error"
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
-
一个唯一的不透明origin
- header list
- referrer policy
-
"no-referrer"
- credentials mode
-
"include"
- mode
-
"no-cors"
该算法中的带凭据fetch可能导致计时攻击,泄露用户身份,相关讨论见此处。
-
令accountsList为null。
-
发起请求,参数为request和globalObject,并设置processResponseConsumeBody如下,输入为response response和responseBody:
-
令json为用response和responseBody执行extract the JSON fetch response的结果。
-
转换json为
IdentityProviderAccountList
,赋值给accountsList。 -
如前两步有异常,则设accountsList为失败。
返回的账户列表应校验重复id,详见讨论。
-
-
等待accountsList设置完成。
-
返回accountsList。
dictionary {
IdentityProviderAccount required USVString id ;required USVString name ;required USVString USVString given_name ;USVString picture ;sequence <USVString >approved_clients ;sequence <DOMString >login_hints ;sequence <DOMString >domain_hints ; };dictionary {
IdentityProviderAccountList sequence <IdentityProviderAccount >; };
accounts
IdentityProviderAccount
account和globalObject,执行如下步骤:
-
如果pictureUrl失败,则终止步骤。用户代理可使用默认头像。
-
令pictureRequest为如下新建的请求:
- url
-
pictureUrl
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"image"
- origin
-
一个唯一的不透明origin
- referrer policy
-
"no-referrer"
- credentials mode
-
"omit"
- mode
-
"no-cors"
-
发起请求,参数为pictureRequest和globalObject,并设置processResponseConsumeBody如下,输入为response和responseBody:
-
如果responseBody为null或失败,用户代理可选择任意默认头像,并与account关联。
-
否则,将responseBody解码为图片,如果成功则与account关联。这样用户代理可在展示account弹窗时使用该图片。
-
2.3.7. 获取身份声明
获取身份声明算法在用户授权使用FedCM并指定IDP账户后调用。它会拉取身份声明端点,以获得将要提供给RP的令牌。
USVString
accountId,布尔值disclosureTextShown,布尔值isAutoSelected,IdentityProviderRequestOptions
provider,IdentityProviderAPIConfig
config,
以及globalObject,执行如下步骤。返回IdentityCredential
或失败。
-
令tokenUrl为用provider、config["
id_assertion_endpoint
"]和globalObject执行computing the manifest URL的结果。 -
如果tokenUrl失败,则返回失败。
-
令requestBody为用如下列表执行urlencoded serializer的结果:
-
令request为如下新建的请求:
- url
-
tokenUrl
- method
-
"POST"
- body
-
对requestBody执行UTF-8编码
- redirect mode
-
"error"
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
- header list
-
一个列表,包含一个header,name为
Accept
,value为application/x-www-form-urlencoded
- credentials mode
-
"include"
- mode
-
"cors"
-
令credential为null。
-
发起请求,参数为request和globalObject,并设置processResponseConsumeBody如下,输入为response response和responseBody:
-
令json为用response和responseBody执行extract the JSON fetch response的结果。
-
转换json为
IdentityProviderToken
,赋值给token。 -
如前两步有异常,则设credential为失败并返回。
-
令credential为用globalObject的realm新建的
IdentityCredential
。 -
设credential的
token
为token。 -
设credential的
isAutoSelected
为isAutoSelected。
-
-
等待credential设置完成。
-
返回credential。
dictionary {
IdentityProviderToken required USVString token ; };
2.3.8. 请求注册权限
IdentityProviderAccount
或失败。
-
断言accountsList的长度大于1。
-
展示账户选择器,显示accountsList中的选项。
-
令account为用户在账户选择器手动选中的
IdentityProviderAccount
,若未选中则为失败。 -
返回account。
请求注册权限算法会拉取客户端元数据端点, 等待用户授权使用指定账户,并返回用户是否授权。
IdentityProviderAccount
account,
IdentityProviderAPIConfig
config,IdentityProviderRequestOptions
provider,以及globalObject,执行如下步骤。返回布尔值。
-
断言:这些步骤在并行运行。
-
令metadata为用config、provider和globalObject执行获取客户端元数据的结果。
-
提示用户,征得创建账户的明确意愿。用户代理可以用
IdentityProviderBranding
决定UI样式。此外:-
如果metadata不失败,且metadata["
privacy_policy_url
"] 已定义,并且provider的clientId
不在account["approved_clients
"]列表中, 用户代理必须显示metadata["privacy_policy_url
"]链接。 -
如果metadata不失败,且metadata["
terms_of_service_url
"] 已定义,并且provider的clientId
不在account["approved_clients
"]列表中, 用户代理必须显示metadata["terms_of_service_url
"]链接。 -
用户代理可以用
context
自定义弹窗。
-
-
如果用户未授权,则返回false。
-
用provider、account和globalObject执行创建RP与IdP账户连接。
-
返回true。
IdentityProviderAPIConfig
config和
IdentityProviderRequestOptions
provider,执行如下步骤。返回IdentityProviderClientMetadata
或失败。
-
令clientMetadataUrl为用provider、config["
client_metadata_endpoint
"]和globalObject执行computing the manifest URL的结果。 -
如果clientMetadataUrl失败,则返回失败。
-
令request为如下新建的请求:
- url
-
clientMetadataUrl
- redirect mode
-
"error"
- client
-
null
- window
-
"no-window"
- service-workers mode
-
"none"
- destination
-
"webidentity"
- origin
- header list
- credentials mode
-
"omit"
- mode
-
"no-cors"
-
令metadata为null。
-
发起请求,参数为request和globalObject,并设置processResponseConsumeBody如下,输入为response response和responseBody:
-
令json为用response和responseBody执行extract the JSON fetch response的结果。
-
转换json为
IdentityProviderClientMetadata
,赋值给metadata。 -
如前两步有异常,则设metadata为失败。
-
-
等待metadata设置完成。
-
返回metadata。
dictionary {
IdentityProviderClientMetadata USVString privacy_policy_url ;USVString terms_of_service_url ; };
2.3.9. 辅助算法
以下辅助算法用于 FedCM 流程中。
-
在globalObject的网络任务源上排队一个全局任务,以:
-
用processResponseConsumeBody作为processResponseConsumeBody,发起request。
-
IdentityProviderRequestOptions
provider、string manifestString和globalObject时,执行如下步骤。返回URL或失败。
-
断言:这些步骤在网络任务源上运行。
-
如果response是网络错误,或者其状态不是ok 状态, 抛出新的"
NetworkError
"DOMException
。 -
令mimeType为用response的header list执行提取 MIME TYPE的结果。
-
如果mimeType失败或不是JSON MIME Type, 抛出新的"
NetworkError
"DOMException
。 -
令json为用responseBody执行解析 JSON 字节为 JavaScript 值的结果。
-
如果json是解析异常,抛出新的"
NetworkError
"DOMException
。 -
返回json。
IdentityProviderAPIConfig
config、IdentityProviderConfig
provider和globalObject,执行如下步骤。返回成功或失败。
-
断言:这些步骤在并行中运行。
-
令loginUrl为 null。
-
在globalObject的DOM 操作任务源上排队一个全局任务,将loginUrl设为用config.
login_url
执行url 解析器的结果。 -
等待loginUrl非 null。
-
断言:loginUrl不是失败(用户代理之前已检查config.
login_url
是有效 URL)。 -
令queryList为新的列表。
-
如果provider的
loginHint
非空,将("login_hint",loginHint
)追加到queryList。 -
如果provider的
domainHint
非空,将("domain_hint",domainHint
)追加到queryList。 -
如果queryList不为空:
-
令queryParameters为用queryList执行urlencoded serializer的结果。
-
如果loginUrl的query非空,则在queryParameters前加上"&"。
-
将queryParameters追加到loginUrl的query。
-
-
用loginUrl创建一个新的顶层可导航对象。
-
用户代理可以设置浏览上下文特性,或以实现自定义方式影响该可导航对象的展示。
-
等待以下条件之一:
-
用户关闭该浏览上下文:返回失败。
-
在此新可导航对象上下文中调用
IdentityProvider
.close
:
-
2.4. IdentityProvider 接口
本规范引入了IdentityUserInfo
字典以及IdentityProvider
接口:
dictionary {
IdentityUserInfo USVString ;
USVString ;
name USVString ;
givenName USVString ; }; [
picture Exposed =Window ,SecureContext ]interface {
IdentityProvider static undefined ();
close static Promise <sequence <IdentityUserInfo >>(
getUserInfo IdentityProviderConfig ); };
config
待决定 IdentityProvider
是否为getUserInfo()
方法的正确位置。
提供close
函数用于通知浏览器登录流程已结束。除了 HTTP 头之外,还需要此函数,因为即使用户已登录,登录流程可能还未真正结束;例如,IDP可能希望提示用户验证手机号码。为支持此类流程,IDP必须在流程真正结束时调用close
。
更多细节参见显示 IDP 登录对话框算法。
IdentityUserInfo
表示用户的账户信息。只有用户已通过 FedCM API 登录过RP后,信息才会暴露给IDP。即,当存在某个account,使得已连接账户集合包含三元组(RP,IDP,account)时才暴露。信息内容与accounts
endpoint一致。IDP可通过调用getUserInfo()
静态方法(在与其configURL
同源的
iframe 内)获取此信息。
const userInfo= await IdentityProvider. getUserInfo({ configUrl: "https://idp.example/fedcm.json" , clientId: "client1234" }); if ( userInfo. length> 0 ) { // 由 IDP 决定如何显示返回的账户信息。 const name= userInfo[ 0 ]. name; const givenName= userInfo[ 0 ]. givenName; const displayName= givenName? givenName: name; const picture= userInfo[ 0 ]. picture; const email= userInfo[ 0 ]. email; }
getUserInfo()
方法,并传入 IdentityProviderConfig
provider 时,执行以下步骤:
-
令 globalObject 为 当前全局对象。
-
令 document 为 globalObject 的 关联文档。
-
如果 document 不允许使用 identity-credentials-get 策略受控特性,则抛出 "
NotAllowedError
"DOMException
。 -
如果没有 account,使得 已连接账户集合 包含用 计算已连接账户键(输入 account, provider, globalObject)得到的结果,则 拒绝 promise,错误为 "
NetworkError
"DOMException
。 此检查可通过遍历 已连接账户集合,或用单独数据结构加快查找。 -
令 configUrl 为用 provider 的
configURL
和 globalObject 执行 parse url 的结果。 -
如果 configUrl 失败,则抛出 "
InvalidStateError
"DOMException
。 -
如果 document 的 origin 与 configUrl 的 origin 不 同源,则抛出 "
InvalidStateError
"DOMException
。 -
对传入的 configUrl 进行 内容安全策略第3级 检查,使用 connect-src 指令。如果失败,则抛出新的 "
NetworkError
"DOMException
。 -
如果 globalObject 的 navigable 是 顶层可遍历对象,则抛出新的 "
NetworkError
"DOMException
。 -
如果用户已在 globalObject 的 navigable 的 顶层遍历对象上禁用 FedCM API,则抛出新的 "
NetworkError
"DOMException
。 -
令 promise 为新的
Promise
。 -
以下步骤 并行执行:
-
令 config 为用 获取配置文件,输入 provider 和 globalObject 得到的结果。
-
如果 config 失败,则 拒绝 promise,错误为新的 "
NetworkError
"DOMException
。 -
令 accountsList 为用 获取账户,输入 config、provider 和 globalObject 得到的结果。
-
令 hasAccountEligibleForAutoReauthentication 为 false。
-
对 accountsList 中每个 account:
-
如果 account["
approved_clients
"] 非空,且不 包含 provider 的clientId
, 则跳过。注意:这允许 IDP 覆盖账户是否为回访账户。 例如用户通过其他渠道断开账户时很有用。
-
如果 account 用 自动重新认证资格判断(输入 provider 和 globalObject)结果为 true,则设 hasAccountEligibleForAutoReauthentication 为 true。
-
-
如果 hasAccountEligibleForAutoReauthentication 为 false,则 拒绝 promise,错误为新的 "
NetworkError
"DOMException
。 -
令 userInfoList 为新 列表。
-
对 accountsList 中每个 account:
-
追加一个
IdentityUserInfo
到 userInfoList,其字段如下:
-
-
3. 身份提供方 HTTP API
本节为非规范性内容。
IDP公开了一系列 HTTP 端点:
-
§ 3.1 Well-Known 文件,用于指向 Manifest
-
一个约定位置的§ 3.2 配置文件,用于指向其他端点
-
一个§ 3.6 断开连接端点(如果支持
disconnect
)。
FedCM API 允许网站请求浏览器执行多种网络请求。浏览器必须以不会让用户被跟踪(例如被冒充的IDP攻击)方式执行这些请求。下表展示了相关网络请求信息:
端点 | cookies | client_id | origin |
manifests | 否 | 否 | 否 |
accounts_endpoint | 是 | 否 | 否 |
client_metadata_endpoint | 否 | 是 | 是 |
id_assertion_endpoint | 是 | 是 | 是 |
disconnect_endpoint | 是 | 是 | 是 |
3.1. Well-Known 文件
注意: 浏览器使用Well-Known 文件来防止§ 7.3.1 Manifest 指纹识别。
IDP在预定义位置提供Well-Known 文件,具体为 IDP 路径 ".well-known" 下的 "web-identity" 文件。
在获取配置文件算法中会拉取Well-Known 文件:
(a) 不带 cookies,
(b) 带 Sec-Fetch-Dest 头并设置为 webidentity
,
(c) 不暴露 RP,即在 Origin 或 Referer 头中不出现 RP 信息。
例如:
GET /.well-known/web-identity HTTP / 1.1 Host : idp.example Accept : application/json Sec-Fetch-Dest : webidentity
该文件会被解析为 IdentityProviderWellKnown
JSON 对象。
IdentityProviderWellKnown
JSON 对象语义如下:
provider_urls
(必需), 类型为 sequence<USVString>-
指向有效的§ 3.2 配置文件的 URL 列表。
3.2. 配置文件
配置文件起到发现IDP其他端点的作用。
(a) 不带 cookies,
(b) 带 Sec-Fetch-Dest 头并设置为 webidentity
,
(c) 不暴露 RP,即在 Origin 或 Referer 头中不出现 RP 信息,
(d) 不跟随 HTTP 重定向。
例如:
响应体必须是能够转换为IdentityProviderAPIConfig
的 JSON 对象(不会抛出异常)。
IdentityProviderAPIConfig
对象成员语义如下:
accounts_endpoint
,类型为 USVString-
指向符合§ 3.3 账户端点API的 HTTP API 的 URL。
client_metadata_endpoint
, 类型为 USVString-
指向符合§ 3.4 客户端元数据API的 HTTP API 的 URL。
id_assertion_endpoint
, 类型为 USVString-
指向符合§ 3.5 身份声明端点API的 HTTP API 的 URL。
disconnect_endpoint
, 类型为 USVString-
指向符合§ 3.6 断开连接端点API的 HTTP API 的 URL。
branding
,类型为 IdentityProviderBranding-
一组
IdentityProviderBranding
选项。
IdentityProviderBranding
使IDP可以表达品牌偏好,用户代理可用其自定义权限弹窗。
注意: 品牌偏好特意设计为高层/抽象(而不是针对特定UI结构),便于不同用户代理提供不同UI体验,并能独立演化。
成员语义如下:
background_color
,类型为 USVStringcolor
,类型为 USVString-
品牌小部件文字颜色。
icons
,类型为 sequence<IdentityProviderIcon>-
IdentityProviderIcon
对象列表。 name
,类型为 USVString-
IDP的用户可识别名称。
注意: 品牌偏好特意设计为高层/抽象(并非针对具体UI结构),便于不同用户代理提供不同UI体验,并能独立演化。
IdentityProviderIcon
成员语义如下:
url
, 类型为 USVString-
指向图标图片的 url,须为正方形且单分辨率(非多分辨率 .ico)。图标须符合maskable规范。
size
, 类型为 unsigned long-
正方形图标的宽/高。如果为矢量图(如 SVG),可省略 size。
注意: 用户代理会为开发者提供的图标保留正方形空间。如果图标不是正方形,用户代理可能选择不显示、裁剪正方形、或转换为正方形显示。
颜色是 CSS <color> 语法的子集,支持 <hex-color>、hsl()、rgb()以及<named-color>。
例如:
{ "accounts_endpoint" : "/accounts" , "client_metadata_endpoint" : "/metadata" , "id_assertion_endpoint" : "/assertion" , "disconnect_endpoint" : "/disconnect" , "branding" : { "background_color" : "green" , "color" : "#FFEEAA" , "icons" : [{ "url" : "https://idp.example/icon.ico" , "size" : 25 }], "name" : "IDP Example" } }
3.3. 账户端点
账户端点 提供用户在 IDP 上的账户列表。
(a) 带有 IDP cookies,
(b) 带有 Sec-Fetch-Dest 头并设置为 webidentity
,
(c) 不暴露 RP,即在 Origin 或 Referer 头中不出现 RP 信息,
(d) 不跟随 HTTP 重定向。
例如:
GET /accounts_list HTTP / 1.1 Host : idp.example Accept : application/json Cookie : 0x23223 Sec-Fetch-Dest : webidentity
响应体必须是能够转换为IdentityProviderAccountList
的 JSON 对象(不会抛出异常)。
每个 IdentityProviderAccount
应包含如下成员:
id
, 类型为 USVString-
账户唯一标识符。
name
,类型为 USVString-
用户全名。
email
,类型为 USVString-
用户邮箱地址。
given_name
,类型为 USVString-
用户名。
picture
,类型为 USVString-
账户头像的 URL。
approved_clients
, 类型为 sequence<USVString>login_hints
,类型为 sequence<DOMString>domain_hints
,类型为 sequence<DOMString>-
与该账户匹配的所有域提示字符串列表。 RP可用
domainHint
请求只展示匹配某个值或包含某域提示的账户。
例如:
{ "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" : [ "john_doe" ], "domain_hints" : [ "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" : [ "email=johhny@idp.example" , "id=5678" ], "domain_hints" : [ "idp.example" ], }] }
澄清用户未登录时 IDP API 响应。
3.4. 客户端元数据端点
客户端元数据端点提供关于 RP 的元数据。
(a) 不带 cookies,
(b) 带 Sec-Fetch-Dest 头并设置为 webidentity
,
(c) 带 RP 的 origin 头,
(d) 不跟随 HTTP 重定向。
用户代理还会传递 client_id。
例如:
GET /client_medata?client_id=1234 HTTP / 1.1 Host : idp.example Origin : https://rp.example/ Accept : application/json Sec-Fetch-Dest : webidentity
响应体必须是能够转换为IdentityProviderClientMetadata
的 JSON 对象(不会抛出异常)。
IdentityProviderClientMetadata
对象成员语义如下:
例如:
{ "privacy_policy_url" : "https://rp.example/clientmetadata/privacy_policy.html" , "terms_of_service_url" : "https://rp.example/clientmetadata/terms_of_service.html" }
3.5. 身份声明端点
身份声明端点负责生成包含用户签名声明的新令牌。
(a) 作为POST请求,
(b) 带有IDP cookies,
(c) 带有 RP 的 origin 头,
(d) 带有Sec-Fetch-Dest头并设置为webidentity
,
(e) 不跟随HTTP 重定向。
请求体为 application/x-www-form-urlencoded
格式,包含如下参数:
client_id
nonce
-
请求随机数
account_id
-
所选账户标识符。
disclosure_text_shown
-
用户代理是否明确向用户展示了IDP将向RP共享哪些具体信息(如" idp.example 将与你的 name、email... 分享给 rp.example"),用于请求注册权限算法中的新用户场景。它作为用户代理向IDP的保证,在需要时确实向用户展示了服务条款和隐私政策。
例如:
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=Ct60bD&disclosure_text_shown=true
响应体必须是能够转换为IdentityProviderToken
的 JSON 对象(不会抛出异常)。
每个IdentityProviderToken
应包含如下成员:
token
,类型为 USVString-
结果令牌。
token
的内容对用户代理是不可见的,可以包含IDP希望传递给RP以完成登录的任意内容。因此,RP需要负责对token
进行验证。验证方式可参考定义的令牌验证算法,例如OIDC Connect Core
§ IDTokenValidation。
注意:对于IDP,可考虑账户的可移植性。可移植性完全由IDP决定,可通过多种机制实现(例如OIDC 的 Account Porting)。
例如:
3.6. 断开连接端点
断开连接端点负责断开之前已建立的 RP 与 IDP账户的联合登录连接,并返回账户的id
,以便用户代理可以将其从已连接账户集合中移除。
断开连接端点会在调用disconnect
方法时拉取:
(a) 作为POST请求,
(b) 带有IDP cookies,
(c) 带有RP 的 origin 头,
(d) 带有Sec-Fetch-Dest头并设置为webidentity
,
(e) 不跟随HTTP 重定向,
(f) 请求模式为 "cors" mode。
请求体为 application/x-www-form-urlencoded
格式,包含如下参数:
例如:
POST /fedcm_disconnect_endpoint HTTP / 1.1 Host : idp.example Origin : https://rp.example/ Content-Type : application/x-www-form-urlencoded Cookie : 0x23223 Sec-Fetch-Dest : webidentity client_id=client1234&account_hint=hint12
如果断开连接失败,IDP可以返回错误;如果成功,响应体必须是能够转换为DisconnectedAccount
的 JSON 对象(不会抛出异常)。
account_id
,类型为 USVString-
已成功断开连接的账户
id
。
IDP必须返回account_id
,因为它可能与account_hint
不同,且此ID可让用户代理将账户从已连接账户集合中断开。如果IDP返回错误或用户代理未找到IDP返回的ID对应的账户,则所有关联的(RP, IDP)账户都会从已连接账户集合中移除。
4. 权限策略集成
FedCM 定义了一个以字符串
"identity-credentials-get"
标识的策略受控特性。它的默认允许列表为 "self"
。
Document
的
权限策略决定该文档的内容是否允许通过 浏览器
API 获取凭证对象。尝试在
未被允许使用 identity-credentials-get
特性的文档中调用
navigator.credentials.get({identity:..., ...})
时,会返回一个被拒绝的 promise,错误为 "NotAllowedError
"
DOMException
。
可通过 Permissions Policy 所述机制控制此限制。
注意: Credential Management Level 1
中定义的算法会实际进行权限策略评估。因为此类策略评估必须在访问当前设置对象时执行。由本规范修改的内部方法
无法访问设置对象,因为它们由 CredentialsContainer
的
请求 Credential 抽象操作并行调用。
5. 用户代理自动化
为了实现用户代理自动化和网站测试,本文件定义了下述 WebDriver 扩展命令,用于与任何活动的 FedCM 对话框交互。
5.1. 扩展能力
能力 | 键 | 值类型 | 描述 |
---|---|---|---|
联合凭证管理支持 | "fedcm:accounts"
| boolean | 表示端点节点是否支持所有联合凭证管理命令。 |
在校验能力时,校验 "fedcm:accounts"
与 value
的扩展专用子步骤如下:
-
如果
value
不是 布尔值,则返回带有 WebDriver 错误 和 错误码 invalid argument。 -
否则,将
deserialized
设为value
。
在匹配能力时,匹配 "fedcm:accounts"
与 value
的扩展专用步骤如下:
-
如果
value
为true
且 端点节点不支持任何联合凭证管理命令, 则匹配失败。 -
否则,匹配成功。
5.2. 取消对话框
HTTP 方法 | URI 模板 |
---|---|
POST | /session/{session id}/fedcm/canceldialog
|
远端步骤如下:
-
如果当前没有打开的 FedCM 对话框,则返回带有 WebDriver 错误 和 错误码 no such alert。
-
关闭对话框,并按照用户未选择账户而取消对话框的方式继续创建 IdentityCredential算法。
-
返回成功,数据为
null
。
5.3. 选择账户
HTTP 方法 | URI 模板 |
---|---|
POST | /session/{session id}/fedcm/selectaccount
|
远端步骤如下:
-
如果 parameters 不是 JSON 对象,则返回带有 WebDriver 错误 和 错误码 invalid argument。
-
如果当前没有打开的 FedCM 对话框,则返回带有 WebDriver 错误 和 错误码 no such alert。
-
令 accountIndex 为从 parameters 取名为
"accountIndex"
的属性的结果。 -
如果 accountIndex 为
undefined
或小于 0 或大于等于当前流程中用户可选择账户的数量,则返回带有 WebDriver 错误 和 错误码 invalid argument。 -
关闭对话框,并按照用户选择了由 accountIndex 指定的账户并(如适用)授权注册的方式继续创建 IdentityCredential算法。
-
返回成功,数据为
null
。
5.4. 点击对话框按钮
HTTP 方法 | URI 模板 |
---|---|
POST | /session/{session id}/fedcm/clickdialogbutton
|
远端步骤如下:
-
如果 parameters 不是 JSON 对象,则返回带有 WebDriver 错误 和 错误码 invalid argument。
-
令 dialogButton 为从 parameters 取名为
"dialogButton"
的属性的结果。 -
如果 dialogButton 不是字符串 "
ConfirmIdpLoginContinue
",则返回带有 WebDriver 错误 和 错误码 invalid argument。 -
如果当前没有打开的 FedCM 对话框或该对话框不是确认 IDP 登录对话框,则返回带有 WebDriver 错误 和 错误码 no such alert。
-
模拟用户点击确认 IDP 登录对话框中的“继续”按钮,并启动登录流程。
-
返回成功,数据为
null
。
5.5. 账户列表
HTTP 方法 | URI 模板 |
---|---|
GET | /session/{session id}/fedcm/accountlist
|
远端步骤如下:
-
如果当前没有打开的 FedCM 对话框,则返回带有 WebDriver 错误 和 错误码 no such alert。
-
令 accounts 为当前流程中用户可选或已选账户的列表。
-
令 list 为空列表。
-
对 accounts 中每个 account:
-
令 accountState 为用 account 和该 account 所属的
IdentityProviderRequestOptions
执行计算连接状态算法的结果 -
-
accountId
设为账户的id
-
email
设为账户的email
-
name
设为账户的name
-
givenName
设为账户的given_name
(如有) -
pictureUrl
设为账户的picture
(如有) -
idpConfigUrl
设为该账户所属 IDP 的configURL
-
loginState
如果 accountState 为断开连接则为"SignUp"
,否则为"SignIn"
-
termsOfServiceUrl
如有则设为terms_of_service_url
,且loginState
为"SignUp"
时有效,否则为undefined
-
privacyPolicyUrl
如有则设为privacy_policy_url
,且loginState
为"SignUp"
时有效,否则为undefined
-
-
-
返回成功,数据为 list。
5.6. 获取标题
HTTP 方法 | URI 模板 |
---|---|
GET | /session/{session id}/fedcm/gettitle
|
注意:此命令允许自动化验证context api是否被正确应用
远端步骤如下:
-
如果当前没有打开的 FedCM 对话框,则返回带有WebDriver 错误 和错误码no such alert。
-
令data为一个对象字典,属性如下:
-
title
为当前打开对话框的标题 -
subtitle
为当前打开对话框的副标题(如有)
-
-
返回成功,数据为data。
5.7. 获取对话框类型
HTTP 方法 | URI 模板 |
---|---|
GET | /session/{session id}/fedcm/getdialogtype
|
远端步骤如下:
-
如果当前没有打开的 FedCM 对话框,则返回带有WebDriver 错误 和错误码no such alert。
-
令type为如下字符串:"AutoReauthn" 表示用户正在自动重新认证, "AccountChooser" 表示对话框为账户选择器,或 "ConfirmIdpLogin" 表示对话框为确认 IDP 登录对话框。
-
返回成功,数据为type。
5.8. 设置延迟开关
HTTP 方法 | URI 模板 |
---|---|
POST | /session/{session id}/fedcm/ setdelayenabled
|
远端步骤如下:
-
如果 parameters 不是 JSON 对象,则返回带有WebDriver 错误和错误码invalid argument。
-
令enabled为从parameters取名为
"enabled"
属性的结果。 -
如果enabled为
undefined
或不是布尔值,则返回带有WebDriver 错误 和错误码invalid argument。 -
如果enabled为 false,则禁用promise 拒绝延迟;否则重新启用。
-
返回成功,数据为
null
。
5.9. 重置冷却时间
HTTP 方法 | URI 模板 |
---|---|
POST | /session/{session id}/fedcm/resetcooldown
|
远端步骤如下:
-
如果用户代理使用冷却延迟(即用户关闭对话框后一段时间内禁用 API),则该命令会重置冷却状态,使下次 FedCM 调用可再次成功。
-
返回成功,数据为
null
。
6. 安全性
本节提供了 FedCM API 的部分安全性考量。注意,§ 7 隐私有单独章节。
6.1. 内容安全策略
FedCM API 首次触发的 fetch 是 manifest 列表(公开)和配置文件。想象一个被RP 引入并以其身份运行的恶意脚本,尝试对恶意IDP(RP 不信任的)执行 FedCM API 调用。如果调用成功,会在RP上展示浏览器 UI,选择登录到恶意IDP。该恶意IDP可试图欺骗用户。防护措施是内容安全策略3级检查,由于恶意IDP的 manifest 源不在 RP 配置的 allowlist 中,检查会失败,从而阻止 FedCM UI 展示。后续 fetch 都与配置文件同源,或至少依赖配置文件内容,无需额外检查。
非同源 fetch 例如品牌图标。用户代理不会对这些执行内容安全策略3级检查,因为它们直接由 manifest 指定,且图片渲染由用户代理完成,RP 无法影响或读取图片内容。
6.2. Sec-Fetch-Dest 头
FedCM API 在IDP引入了多个非静态端点,需要防止 XSS 攻击。为此,FedCM API
在 Sec-Fetch-Dest 头中引入新值,为禁止请求头。FedCM API
发起的请求中该头为webidentity
,此值无法由任意网站设置,因此IDP可确信该请求来自 FedCM 浏览器,而不是网站试图发起 XSS 攻击。IDP 应在收到的凭证请求中检查该头,确保请求来自用户代理。恶意方无法滥用
FedCM API,因此该机制足以保护新 IDP 端点。
6.3. CORS 头
FedCM API 允许身份声明端点的响应被共享给RP。因此要求IDP在拉取此端点时必须使用 "cors" mode 明确同意共享。这样即使服务端忽略 Sec-Fetch-Dest,也无法忽略 CORS,否则 fetch 会失败。
6.4. 浏览器界面冒充
FedCM API 引入了新的(可信)用户代理 UI,用户代理可选择将此 UI 完全覆盖页面内容,原因之一是页面触发了该 UI。这带来潜在风险:恶意站点试图仿造 FedCM UI,获取用户信任,以为自己正在与浏览器界面交互,进而向站点泄露仅愿意向浏览器透露的信息(如其他网站的用户名/密码)。要做到这一点很难,因为 FedCM UI 使用 IDP 账户元数据,恶意网站无法获取。如果是恶意网站,则不会知道用户账户,除非用户已被攻陷。站点可能能猜测用户身份,因此浏览器应尽量提供难以仿造、能清晰展示参与域名的 UI。总体而言,攻击者仅用内容区(如 iframe)能实现的 UI,获取敏感信息,与 FedCM UI 有明显区别。最后,FedCM UI 只能由顶层 frame 触发(或获得顶层 frame 明确授权的 iframe),特权 UI 仅在顶层 frame 需要时展示,隐藏的 iframe 无法强制 FedCM UI 覆盖主页面重要内容。
7. 隐私
本节旨在全面介绍 Web 联合身份相关的隐私风险,以便评估浏览器中介设计的隐私风险和收益。
7.1. 参与者
本节介绍 API 调用中涉及的四类参与者及其行为预期。
-
用户代理实现§ 2 浏览器 API,并控制 RP 和 IDP 内容的执行环境。用户代理被用户信任,RP 和 IDP 也传递信任。
-
RP 是调用 FedCM API 以认证用户或获取用户信息的网站。任何站点都可调用 API,因此 RP 不一定会限制收集的信息,也不一定会以合理方式使用这些信息。
-
IDP 是 FedCM 调用的第三方目标网站,尝试获取令牌。通常 IDP 信任度高于 RP,因为已持有用户个人信息,但也可能滥用用户信息。API 指定的 IDP 也可能不是用户已知的 IDP,此时通常并不预先持有用户信息。
-
跟踪者是非 IDP 的第三方网站,可能滥用 FedCM API 跟踪用户跨站行为。跟踪者可能被 RP主动或被动引入(如通过某个动态加载的脚本),但通常不会修改网站 UI,因此难以发现跟踪。成功在多个站点添加跟踪脚本后,跟踪者可用于各种用途,如出售用户信息。跟踪者不应能使用 FedCM API 跟踪用户。
据此,隐私讨论做如下假设:
-
不允许 RP、IDP 或 跟踪者在未经用户授权的情况下获知某用户访问了某站点。即,用户代理需隐藏“某用户访问了某站点”这一事实,除非获得用户许可。可在用户授权后与 RP 和 IDP 分享该信息。特别地,RP 不应在 FedCM 流程收集用户授权前获知用户身份,IDP 不应获知用户访问了哪个站点。
-
用户代理负责判断用户何时授权 IDP 与 RP 通信,从而提供用户身份信息。
-
一旦用户代理确定用户已授权向 RP 提供其账户信息,IDP 和 RP 可获知该用户的账户信息以完成身份识别,但 RP 不应获知其他账户信息。
7.2. 网络请求
本规范确保 FedCM fetch 都与 RP 指定的 provider 同源。原因是带 cookies 的 fetch 会用指定源的 cookies,允许任意源会引入 cookies 共享混乱和隐私风险。最简单的办法是禁用重定向并检查拉取 URL 的源,保证所有 fetch 均同源。
-
配置文件 fetch 不会用于跟踪用户,因为其不带 cookie、client_id 或 referrer。任何人都能发起该请求,信息也视为公开。
-
账户端点 fetch 不会用于跟踪用户,因为仅带 IDP cookies,且不带 client_id 或 referrer。理论上 RP 获得了新能力,限制请求次数可能很重要,但 IDP 已需防御 DoS 攻击。用户代理每页仅允许一次 FedCM 流程,后续请求会立即拒绝。流程只能在用户交互或超时后结束,请求数量不是问题。IDP 可通过该 fetch 获知大量用户信息,详细见下文。
-
客户端元数据端点 fetch 也不能用于跟踪用户,因为它不带 IDP cookies,虽然带 client_id 和 referrer。这样 IDP 可向用户代理传递需要展示的隐私政策和服务条款。除了此处描述的时序攻击,RP 并不会因该 fetch 获益,且 RP 可自行发起该请求,因为不涉及 IDP cookies。
-
令牌 fetch 设计上会暴露用户给 IDP:含 cookies、client_id 和 referrer。因此需要用户与用户代理 UI 交互后才允许,并由 IDP 向 RP 传递身份信息以完成联合登录/注册。RP 或 IDP 不可能在未获用户授权时强制发起令牌 fetch,用户代理无法被伪造或欺骗。
-
断开连接端点只有在用户至少使用过一次 FedCM 流程后才能被拉取,会向 IDP 发送凭证请求,因此用户代理不应允许无限次此类请求。每次断开都会移除至少一个账户,确保该端点不会成为 RP 永久发送带有 IDP cookie 的请求的渠道。
7.3. 攻击场景
本节描述了各种主体试图获取用户信息的场景。主要考虑如下可能性:
在本节中,如果某个参与者直接或间接采取行动,目的是实现上述威胁之一,则视为参与收集信息。
注意:间接串通的例子:RP引入了由IDP提供的脚本,而该IDP意在跟踪用户。
为讨论方便,本文假设第三方 Cookie 默认禁用且不再能用于跟踪机制,并且已针对“跳转跟踪”(使用链接装饰或 postMessage)实施了一些缓解措施。大多数场景都考虑了不依赖这些机制时的用户跟踪方式。另见Pervasive Monitoring Is an Attack。
7.3.1. Manifest 指纹识别
假设 FedCM API 没有两层 Manifest(见创建 IdentityCredential算法),而是直接只有一个 Manifest。这会带来如下指纹攻击:
// 下述代码会拉取 Manifest JSON 文件,Manifest 能直接获知 RP 的来源 :( const cred= await navigator. credentials. get({ identity: { providers: [{ configURL: \`https://idp.example/ ${ window. location. href} \` }] } });
注意:关于该攻击可参见此处。
这里,RP在向IDP拉取 Manifest 时暴露了自身身份。配合 FedCM API 的带凭证请求,IDP可轻易跟踪用户访问的网站,无需用户授权。我们的缓解措施是使用§ 3.1 Well-Known 文件。要求该文件存在于IDP域名根目录,以确保文件名不会引入关于访问的RP的指纹。
整个 Manifest 可以放在这个位置,但实际上只是从这里指向真正的 Manifest。这样设计便于未来灵活扩展,比如某个IDP需要少量常量 Manifest 文件而不是单一文件时,也方便IDP实现,因为 Manifest 文件的修改更可能发生在任意路径而不是根目录,后者更新受限更多。
7.3.2. 时序攻击
在时序攻击中,RP和IDP串通,使IDP能够在未经用户同意的情况下计算(RP的来源,IDP的用户身份)对。该攻击非确定性:因需拼接两个独立信息追踪用户,存在统计误差。但需尽量缓解该攻击,使其在经济上不可行。假定网络请求没有大的指纹向量(如 IP 地址),这些由用户代理决定,难以完全消除,但用户代理应逐步解决。与网络请求相关的信息使时序攻击更易实现,因此更需关注。
注意:该攻击详见此处。
攻击流程如下:
-
RP记录调用 API 的时间 A,并将其发送给IDP,使其通过收到RP客户端元数据 fetch 的时间标记自己。时间 A 与站点相关。
-
随后,向IDP发送一个带凭证请求,但不明确标识RP,但发起时间与前述请求接近。IDP记录该请求到达时间 B,时间 B 与用户身份相关。
-
IDP将时间 A 与时间 B 相关联,以高概率得到(站点,用户身份)对。指纹识别可使关联更准确。
注意,这类关联本就能通过跨域顶层导航实现,但如果 FedCM 提高时序分辨率或对用户不明显(如IDP故意返回空账户给用户代理,使浏览器 UI 不触发),用 FedCM 会加剧问题,让攻击对用户不可见。
用户代理应以可互操作的方式缓解此类攻击,保护用户。
7.3.3. IDP 干扰
引自 Target Privacy Threat Model § hl-intrusion
隐私伤害并不总是来自站点获知信息。
干扰指侵入性行为,打扰或中断他人的生活或活动。干扰会妨碍个人“独处”愿望,消耗其时间或注意力,或打断其活动。
在联合场景下,干扰发生在RP和IDP串通,过度、主动地向用户推荐登录,导致用户体验失衡。类似于不请自来的通知,RP可与IDP串通,强行推动用户登录。
用户代理可通过调节用户控制进行缓解,并根据用户意图或隐私风险提供适当的界面。例如,用户代理可在确信用户意图时展示高亮/打断性的模态对话框,否则展示安静/保守的界面提示。
用户代理还可根据所涉风险控制对用户体验的打扰。例如,交换定向标识符时更确信副作用,可提供更激进的用户体验;交换全局标识符时则更保守。
7.3.4. 跨站点关联
此类攻击发生在多个RP串通,共享其用户数据以进行关联,从而构建更丰富的用户画像。当用户主动向多个服务方(RP)提供姓名、邮箱、手机号等信息时,这些服务方可以合作,将这些信息关联起来,构建用户在合作站点上的活动画像。这有时被称为“合并”,因为本质上是将多个RP账户数据库中的用户记录进行关联。此类关联和画像行为超出用户的控制范围,也完全不在用户代理或IDP的视野之内。
-
用户用 IDP 登录了 RP1(销售珠宝),并向 RP1 提供了自己的邮箱地址 user@email.example
-
用户用 IDP 登录了 RP2(销售房屋),并向 RP2 提供了自己的邮箱地址 user@email.example
-
用户在 RP1 浏览结婚戒指的商品。
-
RP1 通过其他渠道告知 RP2,user@email.example 在购买结婚戒指
-
用户在 RP2 浏览房屋库存。
-
RP2 利用用户在 RP1 购买结婚戒指的事实,对其房屋库存进行广告和筛选。
-
用户很惊讶 RP2 知道他们正在购买结婚戒指。
通过后门渠道联合用户数据的问题,源于可识别用户数据的大量存在。这个问题可以通过发放定向标识符来解决,这种标识符能为用户在某个 IDP 下提供一个有效的身份标识,并且唯一,无法和其他 RP 相关联。过去曾有过类似方案,比如用用户姓名、IDP 和 RP 做一次单向哈希。这类标识符是唯一的,因此无法与其他 RP 相关联。
7.3.5. 未授权数据使用
另一种攻击是RP或IDP将用户信息用于未获用户授权的用途。当用户同意让IDP向RP提供信息时,授权仅限于某些特定用途,比如登录和个性化。例如,RP可能会将这些数据用于用户未预期且未授权的其他用途,比如将邮箱地址出售给垃圾邮件名单。即使使用定向标识符,也仍可能存在垃圾邮件风险。
7.3.6. RP 指纹识别
这种攻击发生在RP通过客户端状态跟踪来识别用户时。任何向 Web 暴露客户端状态的 API 都有成为指纹识别攻击途径的风险。该 API 的目的是让用户向RP提供身份信息,用户应该能够撤销对该身份的访问,例如通过登出。但一个有跟踪意图的RP可以保存状态,从而检测之前登录过的用户:
7.3.7. 二次用途
二次用途指未经个人许可,将收集到的个人信息用于与最初收集目的不同的其他目的。当IDP滥用所收集的信息,将其用于除登录以外的其他目的时,就会发生此类攻击。
现有联合协议要求IDP知晓请求令牌的服务,以实现身份联合。身份提供方可以利用这一点,在用户选择用同一账户进行联合登录的各个网站上建立用户画像。例如,这些画像可以用于在 IDP 控制的网站上向用户投放定向广告。
即使IDP并没有预先存有用户账户信息(例如不是真正的 IDP),这种风险也可能存在,因为 FedCM 发送到IDP的请求是带凭证的。如果RP与IDP串通,通过§ 7.3.2 时序攻击实现跟踪,这种情况更容易发生。
阻止 IDP 跟踪用户很难:由于安全原因,例如防止令牌重用和防止欺诈滥用,RP 必须被编码进身份令牌。已有一些密码学方案能让 IDP对RP不可见,同时仍能防止令牌重用(见 Mozilla 的 personas)。但本规范尚未采用这些方案。
8. 可扩展性
注意:介绍可扩展性机制。
9. 致谢
注意:请补充致谢部分。
10. FPWD 问题
注意:工作组已将下列问题标记为关键待解决问题,必须在发布候选推荐前正式处理。- Issue 240: 用户无法使用 RP 未枚举的 IdP
- Issue 317: 关于账户列表中的邮箱问题
- Issue 319: 支持多个 IDP 使用
- Issue 320: 为何是 Sec-FedCM-CSRF 而不是 Sec-Fetch-Mode
- Issue 352: 与 IDP 共享性能测量
- Issue 407: [Context API] - 授权/与 scope 指定能力的关系
- Issue 428: 在身份声明端点上强制 CORS
- Issue 441: IDP 需支持额外基础设施以支持 FedCM
- Issue 442: 尚未登录的 IDP 在该流程下无成功路径
- Issue 467: FedCM 授权后通过 Storage Access API 访问跨站点 Cookie 的用例?
- Issue 488: 用户表现出登录意图但登录失败后可能产生困惑
- Issue 511: 支持登录到额外账户
- Issue 517: 允许用户代理灵活使用“已连接账户集合”
- Issue 537: 允许 IDP 从同站点子资源设置登录状态
- Issue 552: 允许 IDP 在 eTLD+1 下使用多个配置文件
- Issue 553: 允许 IDP 在不同上下文下暴露不同账户列表
- Issue 555: 允许 IdP 在弹窗中继续并完成请求
- Issue 556: 向身份声明端点传递任意参数
- Issue 559: 允许 RP 有选择地请求用户资料属性
- Issue 578: 允许 IdP 返回 JSON 对象而非字符串给 RP
- Issue 585: 允许 IdP 注册和 RP 按 "type" 匹配
- Issue 587: 为何必须 SameSite=none?
- Issue 599: FedCM 的 OAuth 规范
- Issue 609: 规范要求我们发送 SameSite=Strict Cookie
- Issue 616: 一旦
params
合并进规范,弃用nonce
参数 - Issue 618: 在减少导航跟踪缓解机制中的启发和分类/列表前支持链式认证流程
- Issue 620: 让注册 IdP 更易部署在 eTLD+1
- Issue 625: 已返回账户在 getUserInfo 中优先
- Issue 626: PP/TOS 要求与自动重新认证不同
- Issue 627: 添加 webdriver 命令以打开 PP/TOS