1. 引言
本节不具有规范性。
登录网站比应当更加困难。用户代理在以多种方式改善体验方面处于独特地位,大多数现代用户代理已通过在浏览器中提供一定程度的凭据管理功能来认识到这一点。例如,用户可以保存网站的用户名和密码;这些凭据随后会被自动填充到登录表单中,尽管成功率各不相同。
autocomplete 属性提供了一种声明式机制,网站可以通过该机制与用户代理协作,提高后者检测和填充登录表单的能力,方法是将特定字段标记为“用户名”或“密码”。用户代理实现了多种检测启发法,以与未在标记中提供此详细信息的网站协作。
虽然这种启发式和声明式检测的结合工作得相对较好,但现状留下了一些检测存在问题的巨大差距。具有不常见登录机制(例如通过 XMLHttpRequest [XMLHTTPREQUEST] 提交凭据)的网站很难可靠地检测到,用户希望使用联合身份提供商进行身份验证的情况也越来越普遍。允许网站更直接地与用户代理的凭据管理器交互,一方面可以使凭据管理器更加准确,另一方面可以协助用户进行联合登录。
这些用例在 § 1.1 用例 以及 凭据管理:用例与要求 中进行了更详细的探讨;本规范试图通过定义凭据管理器 API 来解决该文档概述的许多要求,网站可以使用该 API 为用户请求凭据,并请求用户代理在用户成功登录时持久化凭据。
注意:此处定义的 API 有意保持简洁:它本身并不打算提供身份验证,而是仅限于提供一个接口,连接到现有用户代理实现的现有凭据管理器。该功能在目前就很有价值,无需供应商或作者付出巨大的努力。当然,还有很多工作可以做。请参阅 § 9 未来工作,了解我们暂时搁置但可以在此 API 的未来迭代中探索的一些想法。
1.1. 用例
现代用户代理通常为用户提供在登录网站时保存密码的能力,同样也提供在用户返回网站时全自动或半自动地将这些密码填充到登录表单中的能力。从网站的角度来看,这种行为是完全不可见的:网站不知道密码已被存储,也不知道密码已被填充。这既有优点也有缺点。一方面,用户代理的密码管理器无论网站是否配合都能工作,这对用户来说是非常好的。另一方面,密码管理器的行为是一个脆弱且专有的启发式混合体,旨在检测和填充登录表单、密码修改表单等。
现状中的几个问题尤其值得注意。
-
用户代理在协助用户使用联合身份提供商方面非常困难。虽然检测用户名/密码表单提交相当简单,但要可靠地检测通过第三方进行的登录却非常困难。如果网站能够帮助用户代理理解典型联合登录操作背后相关的重定向意图,那将是非常好的。
-
同样,用户代理在检测比简单用户名/密码表单更深奥的登录机制方面也存在困难。为了改善体验并更好地控制呈现,作者越来越多地通过
XMLHttpRequest或类似机制异步登录用户。这对用户有好处,但对于用户代理集成到密码管理器中来说很困难。如果网站能够帮助用户代理理清他们选择使用的登录机制,那将是非常好的。 -
最后,如果网站明确告知用户代理凭据已更改,则修改密码的支持将比目前更好。
2. 核心 API
从开发者的角度来看,凭据是一个对象,允许开发者为特定操作做出身份验证决策。本节定义了一个通用且可扩展的 Credential 接口,作为本文档和其他文档中定义的凭据的基类,以及挂载在 navigator.credentials.* 上的一组 API,使开发者能够获取它们。
各种凭据类型在 JavaScript 中分别表示为一个接口,该接口直接或间接地继承自 Credential 接口。本文档定义了两个此类接口:PasswordCredential 和 FederatedCredential。其他规范(例如 [WEBAUTHN])定义了其他凭据类型。
如果一个凭据被特定源接受为该源上的身份验证,则它对该源是有效的。即使凭据在特定时间点有效,用户代理也不能假设同一个凭据在未来的任何时间点仍然有效,原因有两个:
-
如果账户持有人更改了密码,密码凭据可能会停止有效。
-
通过短信收到的令牌制作的凭据可能仅对单次使用有效。
一次性凭据是由凭据来源生成的,这可能是私钥、对联合账户的访问权限、在特定电话号码上接收短信的能力或其他事物。凭据来源不会暴露给 Javascript,也不会在本规范中明确表示。为了统一模型,我们将密码视为一种独立的凭据来源,仅仅通过复制它来创建密码凭据。
即使 UA 不能假设有效的凭据如果再次使用仍然有效,或者产生有效凭据的凭据来源将来能够产生第二个有效凭据,但第二种情况比第一种更有可能。通过记录(使用 store())哪些凭据在过去是有效的,UA 在将来更有可能向用户提供有效的凭据来源。
2.1. 基础设施
凭据管理器是一个应用程序、硬件设备或服务,它存储、组织、管理并允许选择凭据。示例凭据管理器包括数字钱包、密码管理器和通行密钥 (passkey) 管理器。
用户代理必须在内部提供一个凭据存储,这是一个特定于供应商的不透明存储机制,用于记录哪些凭据是有效的。它为凭据访问和持久化提供以下能力:
此外,凭据存储应为源维护一个禁止静默访问标志(除非另有说明,否则设置为 true)。如果源的标志设置为 true,则该源需要用户中介。
注意:用户中介的重要性在 § 5 用户中介 中有更详细的讨论。
注意:凭据存储是用户代理实现本文档中指定的 API 的内部实现细节,不会直接暴露给 Web。为了支持特定凭据类型,其他文档可能会指定更多能力。
本文档依赖于 Infra 标准来获取其算法和正文中使用的许多基础概念 [INFRA]。
每个环境设置对象都有一个关联的激活的凭据类型,这是一个初始为空的集合。
2.1.1. 基础设施算法
2.1.1.1. 与祖先同源
如果以下算法返回 true,则环境设置对象(settings)被认为是与祖先同源的:
-
如果 document 没有浏览上下文,则返回
false。 -
令 origin 为 settings 的源。
-
令 navigable 为 document 的节点导航器。
-
当 navigable 具有非空的父级时:
-
返回
true。
2.1.2. 凭据类型注册表
该注册表将凭据类型(即 [[type]] 值)映射到与给定凭据类型关联的各种值。例如:选项成员标识符(形式上是一个字典成员标识符),其规范在 CredentialCreationOptions 和 CredentialRequestOptions(即“选项字典”)中使用。
注意:此注册表由相关凭据接口对象算法使用。
| 凭据类型 (按字母顺序) | 选项成员标识符 | 适当的接口对象 | Get 权限策略 | Create 权限策略 | 允许在同一 get() 请求中使用的类型
| 规范 | 请求者联系方式 |
|---|---|---|---|---|---|---|---|
| digital-credential | 数字 | DigitalCredential
| digital-credentials-get | null | empty | [DIGITAL-CREDENTIALS] | WICG |
| federated | federated | FederatedCredential
| null | null | 密码 | 本文档:§ 4 联合凭据 | W3C |
| 恒等 (identity) | 恒等 (identity) | IdentityCredential
| identity-credentials-get | null | empty | [FEDCM] | W3C |
| otp | otp | OTPCredential
| otp-credentials | null | empty | [WEB-OTP] | WICG |
| 密码 | 密码 | PasswordCredential
| null | null | federated | 本文档:§ 3 密码凭据 | W3C |
| public-key | publicKey(公钥) | PublicKeyCredential
| publickey-credentials-get | publickey-credentials-create | empty | [WEBAUTHN] | W3C |
2.1.2.1. 注册条目要求与更新流程
-
每个注册条目必须声明在凭据规范的
CredentialCreationOptions和CredentialRequestOptions扩展中使用的字典成员标识符(在注册表中称为选项成员标识符)。 -
每个注册条目必须声明执行该凭据类型的请求
Credential时使用的 Get 权限策略许可,如果未指定权限策略,则为 null。 -
每个注册条目必须声明执行该凭据类型的创建
Credential时使用的 Create 权限策略许可,如果未指定权限策略,则为 null。 -
虽然
get()请求支持在同一请求中查询多种类型,但并非所有组合都被允许。每个注册条目必须声明一组凭据类型,该集合表示该凭据类型在同一get()请求中允许的类型;如果它不能与同一get()请求中的任何其他类型混合,则为空。每个凭据类型都可以在同一get()请求中与自身混合。 -
每个注册条目的规范必须包含请求者的联系信息。
此注册表的更新是对凭据类型注册条目的添加、更改或删除。任何人都可以通过向 webappsec-credential-management 仓库提交 pull request 来请求更新此注册表。Web 应用安全工作组将把它放入即将召开的会议议程中,并通知请求者。对请求的审议和处置由 W3C Web 应用安全工作组通过共识决定。主席随后将通知请求者结果并相应地更新注册表。
2.2. Credential 接口
[Exposed =Window ,SecureContext ]interface {Credential readonly attribute USVString id ;readonly attribute DOMString type ;static Promise <boolean >isConditionalMediationAvailable (); };
id, 类型为 USVString,只读-
凭据的标识符。每种凭据类型的标识符要求各不相同。例如,它可能表示用户名/密码元组的用户名。
type, 类型为 DOMString,只读isConditionalMediationAvailable()-
返回一个
Promise,当且仅当用户代理支持针对该凭据类型的凭据请求中介的conditional方法时,该 Promise 解析 (resolve) 为true,否则解析为false。Credential的isConditionalMediationAvailable()默认实现-
返回一个解析为
false的 promise。
任何支持
conditional中介的凭据类型的规范必须明确覆盖此函数,以解析 (resolve) 为true。注意:如果此函数不存在,则该凭据类型不支持
conditional中介。 -
[[type]]-
Credential接口对象有一个名为[[type]]的内部插槽,它不出所料地包含一个表示凭据类型的字符串。除非另有说明,否则插槽的值为空字符串。请参阅 § 2.1.2 凭据类型注册表 以获取凭据类型列表。注意:
[[type]]插槽的值对于所有实现特定接口的凭据都是相同的,这意味着开发者可以依赖obj.type返回一个明确表示他们正在处理的特定类型Credential的字符串。 [[discovery]]-
Credential接口对象有一个名为[[discovery]]的内部插槽,表示用户代理可以收集给定类型凭据的机制。其值为 "credential store" 或 "remote"。前一个值表示所有可用的凭据信息都存储在用户代理的凭据存储中,而后者表示用户代理可以通过与某些外部设备或服务交互,发现那些未明确表示在凭据存储中的凭据。
与 Tobie/Dominic 讨论接口对象部分,这里和 § 2.5.1 请求 Credential 等处。我不确定我是否理解对了术语。接口原型对象,也许?
一些 Credential 对象是源绑定的:它们包含一个名为 [[origin]] 的内部插槽,用于存储 Credential 可能有效的源。
2.2.1. Credential 内部方法
Credential 接口对象具有多个促进 Credential 对象检索和存储的内部方法,并带有本节定义的默认“无操作”实现。
除非另有说明,否则为继承自 Credential 的接口创建的每个接口对象必须提供至少一个这些内部方法的实现,覆盖 Credential 的默认实现,具体取决于凭据类型。例如:§ 3.2 PasswordCredential 接口、§ 4.1 FederatedCredential 接口 和 [WEBAUTHN]。
2.2.1.1. [[CollectFromCredentialStore]] 内部方法
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 被调用时,传入一个源、一个 CredentialRequestOptions,以及一个布尔值,该布尔值当且仅当调用者的环境设置对象与祖先同源时为真。该算法返回用户代理凭据存储中匹配所提供选项的一组 Credential 对象。如果没有匹配的 Credential 对象可用,则返回的集合为空。Credential 的 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 默认实现
-
返回一个空集。
2.2.1.2. [[DiscoverFromExternalSource]] 内部方法
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 并行调用,传入一个源、一个 CredentialRequestOptions 对象,以及一个布尔值,该布尔值当且仅当调用者的环境设置对象与祖先同源时为真。它在给定所提供选项的情况下,如果可以返回一个 Credential,则返回该凭据;如果没有可用凭据,则返回 null;如果发现失败(例如,错误的选项可能会产生 TypeError),则抛出错误。如果此类 Credential 仅对单次使用或有限时间有效,则此方法负责使用凭据来源生成新的凭据。Credential 的 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 默认实现
-
返回
null。
2.2.1.3. [[Store]] 内部方法
[[Store]](credential, sameOriginWithAncestors) 并行调用,传入一个 Credential,以及一个布尔值,该布尔值当且仅当调用者的环境设置对象与祖先同源时为真。算法在 Credential 持久化到凭据存储后返回。Credential 的 [[Store]](credential, sameOriginWithAncestors) 默认实现
-
抛出
NotSupportedError。
2.2.1.4. [[Create]] 内部方法
[[Create]](origin, options, sameOriginWithAncestors) 并行调用,传入一个源、一个 CredentialCreationOptions,以及一个布尔值,该布尔值当且仅当调用者的环境设置对象与祖先同源时为真。算法要么-
创建一个
Credential,或者 -
不创建凭据并返回
null,或者 -
如果创建由于异常情况失败(例如,错误的选项可能会产生
TypeError),则抛出错误。
创建 Credential 时,它将返回一个接收全局对象并返回继承自 Credential 的接口对象的算法。此算法必须从任务中调用。
注意:此算法的步骤基于每个凭据类型进行定义。
Credential 的 [[Create]](origin, options, sameOriginWithAncestors) 默认实现
-
返回
null。
2.2.2. CredentialUserData 混合项
一些 Credential 对象包含的数据旨在通过提供友好的名称和图标,在凭据选择器中为用户提供人类可读的消歧机制:
[SecureContext ]interface mixin {CredentialUserData readonly attribute USVString name ;readonly attribute USVString iconURL ; };
2.3. navigator.credentials
开发者通过暴露在 CredentialsContainer 接口上的方法检索 Credential 并与用户代理的凭据存储交互,该接口挂载在 Navigator 对象上,作为 navigator.credentials。
partial interface Navigator { [SecureContext ,SameObject ]readonly attribute CredentialsContainer credentials ; };
credentials 属性必须返回与活动文档的浏览上下文关联的 CredentialsContainer。
注意:如 § 6.3 不安全站点 中所述,凭据管理 API 仅在安全上下文中暴露。
[Exposed =Window ,SecureContext ]interface {CredentialsContainer Promise <Credential ?>get (optional CredentialRequestOptions options = {});Promise <undefined >store (Credential credential );Promise <Credential ?>create (optional CredentialCreationOptions options = {});Promise <undefined >preventSilentAccess (); };dictionary {CredentialData required USVString ; };id
get(options)-
当调用
get()时,用户代理必须返回对options执行请求Credential的结果。CredentialsContainer.get(options) 方法的参数。 参数 类型 可为空 (Nullable) 可选 (Optional) 描述 optionsCredentialRequestOptions✘ ✔ 控制请求范围的属性集。 store(credential)-
当调用
store()时,用户代理必须返回对credential执行存储Credential的结果。CredentialsContainer.store(credential) 方法的参数。 参数 类型 可为空 (Nullable) 可选 (Optional) 描述 凭据 (credential)凭据 (Credential)✘ ✘ 要存储的凭据。 create(options)-
当调用
create()时,用户代理必须返回对options执行创建Credential的结果。CredentialsContainer.create(options) 方法的参数。 参数 类型 可为空 (Nullable) 可选 (Optional) 描述 optionsCredentialCreationOptions✘ ✔ 用于创建 Credential的选项。 preventSilentAccess()-
当调用
preventSilentAccess()时,用户代理必须返回对当前设置对象执行禁止静默访问的结果。注意:这里的意图是来自源的信号,表明用户已经登出。也就是说,在点击“登出”按钮后,站点更新用户的会话信息,并调用
navigator.credentials.preventSilentAccess()。这会设置禁止静默访问标志,意味着下一次用户访问时,凭据将不会自动传递回页面。注意:此函数以前称为
requireUserMediation(),该名称应被视为已弃用。
Navigator 对象(navigator)时,用户代理必须创建一个新的 CredentialsContainer 对象,使用 navigator 的相关 Realm,并将其与 navigator 关联。2.3.1. CredentialRequestOptions 字典
为了通过 get() 检索 Credential,调用者在 CredentialRequestOptions 对象中指定一些参数。
注意:CredentialRequestOptions 字典是一个扩展点。如果引入了需要选项的新类型凭据,它们的字典类型将被添加到字典中,以便它们可以传递到请求中。请参阅 § 8.2 扩展点。
dictionary {CredentialRequestOptions CredentialMediationRequirement mediation = "optional";DOMString uiMode ;AbortSignal signal ; };
mediation, 类型为 CredentialMediationRequirement,默认为"optional"-
此属性指定给定凭据请求的中介要求。每个枚举值的含义在下面的
CredentialMediationRequirement中描述。处理详情定义在 § 2.5.1 请求Credential中。 uiMode, 类型为 DOMString-
此属性指定用户代理执行用户中介时,给定凭据请求的用户界面模式。每个值的含义在下面的
CredentialUiMode中描述。处理详情定义在 § 2.5.1 请求Credential中。 signal, 类型为 AbortSignal-
此属性允许开发者中止正在进行的
get()操作。已中止的操作可能会正常完成(通常是在操作完成后才收到中止信号),或者以中止原因拒绝。
unmediated。将其设置为 true 的效果是将 mediation 设置为 "silent",而将其设置为 false 的效果是将 mediation 设置为 "optional"。unmediated 应被视为已弃用;新代码应改为依赖 mediation。
CredentialCreationOptions 或 CredentialRequestOptions(options)的相关凭据接口对象是一组接口对象,收集如下:注意:此算法使用凭据类型注册表。
true,则给定 CredentialRequestOptions(options)被认为是先验可匹配的 (matchable a priori):-
对于 options 的相关凭据接口对象中的每个 interface:
-
如果 interface 的
[[discovery]]插槽的值不是 "credential store",则返回false。
-
-
返回
true。
注意:执行 get(options) 时,仅当提供的 CredentialRequestOptions 是先验可匹配的时,我们才会返回没有用户中介的凭据。如果请求了任何可能需要从某些外部服务发现的凭据类型(OAuth 令牌、安全密钥认证器等),则将需要用户中介来引导发现过程(通过选择联合身份提供商、BTLE 设备等)。
2.3.2. 中介要求
当通过 get(options) 或 create(options) 发出请求时,开发者可以通过选择适当的 CredentialMediationRequirement 枚举值,为用户中介设置具体的要求。
注意:§ 5 用户中介 节更详细地介绍了该概念,以及它如何影响用户代理处理给定源的单个请求的方式。
enum {CredentialMediationRequirement "silent" ,"optional" ,"conditional" ,"required" };
silent-
对于给定的操作,用户中介被抑制。如果操作可以在没有用户参与的情况下执行,那很好。如果需要用户参与,则操作将返回
null,而不是让用户参与。注意:预期的用法是支持“让我保持登录此网站”场景,其中开发者可能希望在用户应该自动登录时静默获取凭据,但直到用户主动选择登录时,才延迟以登录提示打扰用户。
可选-
如果无需用户中介即可为给定操作移交凭据,则会移交。如果需要用户中介,则用户代理将让用户参与决策。
注意:这是
get()的默认行为,旨在服务于开发者有合理信心认为用户希望开始登录操作的情况。例如,如果用户刚刚点击了“登录”,那么如果需要,他们看到凭据选择器就不会感到惊讶或困惑。 conditional-
对于
get(),发现的凭据会在非模态对话框中呈现给用户,并附带请求凭据的源的指示。如果用户在对话框之外进行手势操作,对话框将关闭,而不会解析或拒绝Promise(由get()方法返回),也不会导致用户可见的错误情况。如果用户进行选择凭据的手势,则该凭据将返回给调用者。禁止静默访问标志被视为true,无论其实际值如何:如果发现了适用的凭据,conditional行为总是涉及某种形式的用户中介。如果没有发现凭据,用户代理可能会提示用户采取行动,具体方式取决于凭据类型(例如插入包含凭据的设备)。无论哪种方式,
get()方法都不得立即解析为null,以避免向网站泄露缺乏适用凭据的情况。仅当网站引用的所有凭据接口都已覆盖
isConditionalMediationAvailable()并返回一个解析为true的Promise时,网站才能将conditional传递给get()方法。对于
create(),如果用户之前已同意创建凭据且用户代理知道它最近调解了一次身份验证,那么create()调用可能会在没有额外的突出模态交互的情况下解析。如果用户代理最近没有调解身份验证或者没有获得凭据创建的同意,则该调用必须抛出一个 "NotAllowedError"DOMException。 required(必须)-
用户代理不会在没有用户中介的情况下移交凭据,即使该源的禁止静默访问标志未设置也是如此。
注意:此要求旨在支持重新身份验证或用户切换场景。此外,该要求与特定操作相关联,不会影响该源的禁止静默访问标志。要设置该标志,开发者应调用
preventSilentAccess()。
2.3.3. UI 模式
当通过 get(options) 发出请求时,开发者可以通过选择适当的 CredentialUiMode 枚举值来指定所需用户中介模式。
UI Mode 字段没有默认值。当未指定时,[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 方法必须独立考量 CredentialMediationRequirement,以指定用户代理的行为。
注意: 当 用户调解 发生时,这允许调用者区分不同可用的 用户调解 形式。是否显示 UI 取决于请求的 CredentialMediationRequirement 值。并非 CredentialUiMode 的所有值都与所有 CredentialMediationRequirement 值组合都有意义,并且其交互取决于支持该操作的凭据类型的 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 方法。
enum {CredentialUiMode "immediate" };
immediate-
在调用
get()时,用户代理要么向用户显示模态凭据选择对话框,要么操作会立即完成而不提供任何凭据。这使得可以向用户显示简化的登录 UI,否则网站可以采取替代操作,例如显示其自己的登录页面或继续非登录用户的流程。
如果凭据选择对话框中没有可显示的凭据,则会跳过 用户调解,例如,不会提示用户提供凭据。
注意: 本规范未提供类似于
isConditionalMediationAvailable()的静态方法来检测immediate模式的功能可用性。未来,如果此调解模式扩展到包含其他凭据类型,将需要一种更通用的功能检测方法,能够指示在给定的CredentialMediationRequirement和CredentialUiMode值组合下,哪些凭据类型受支持。
2.3.3.1. 示例
get() 来实现这一点,并传入设置为 "silent" 的 mediation 成员。这确保了已选择放弃用户调解要求(如 § 5.2 Requiring User Mediation 中所述)的用户能够登录,而未选择此类行为的用户也不会因无缘无故弹出的 凭据选择器 而受到干扰。window.addEventListener('load', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'silent'
});
if (credentials) {
// Hooray! Let's sign the user in using these credentials!
}
});
document.querySelector('#sign-in').addEventListener('click', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'optional'
});
if (credentials) {
// Hooray! Let's sign the user in using these credentials!
}
});
注意: MegaCorp, Inc. 也可以完全省略 mediation 成员,因为 "optional" 是其默认值。
required" 的 mediation 成员调用 get() 来确保用户代理要求进行调解。注意: 根据浏览器的安全模型或凭据类型,这可能要求用户以某种方式自行进行身份验证,例如在将凭据交给网站之前输入主密码、扫描指纹等。
document.querySelector('#important-form').addEventListener('submit', async () => {
const credentials = await navigator.credentials.get({
...,
mediation: 'required'
});
if (credentials) {
// Verify that |credentials| enables access, and cancel the submission
// if it doesn't.
} else {
e.preventDefault();
}
});
required" 的 mediation 成员调用 get(),以确保在点击“添加账户”按钮时凭据不会自动返回。document.querySelector('#switch-button').addEventListener('click', e => {
var c = await navigator.credentials.get({
...,
mediation: 'required'
});
if (c) {
// Sign the user in using |c|.
}
});
2.4. CredentialCreationOptions 字典
为了通过 create() 创建 Credential,调用者需要在 CredentialCreationOptions 对象中指定一些参数。
注意: CredentialCreationOptions 字典是一个扩展点。如果引入新的凭据类型,它们将添加到此字典中,以便可以传递到创建方法中。请参阅 § 8.2 Extension Points 以及本文档中引入的扩展:§ 3.2 The PasswordCredential Interface 和 § 4.1 The FederatedCredential Interface。
dictionary {CredentialCreationOptions CredentialMediationRequirement = "optional";mediation AbortSignal signal ; };
signal, 类型为 AbortSignal-
此属性允许开发者中止正在进行的
create()操作。已中止的操作可能会正常完成(通常是在操作结束后才收到中止信号时),或者以 中止原因 拒绝。
2.5. 算法
2.5.1. 请求 Credential
请求 Credential 算法接受一个 CredentialRequestOptions (options),并返回一个 Promise,如果能明确获取到凭据,则该 Promise 解析为 Credential;否则,解析为 null。
-
令 settings 为 当前设置对象。
-
断言:settings 是一个 安全上下文。
-
如果 document 不是 完全活动的,则返回 一个被拒绝的 promise,并带有 "
InvalidStateError"DOMException。 -
如果
options.已 中止,则返回 一个被拒绝的 promise,并带有signaloptions.的 中止原因。signal -
令 interfaces 为 options 的 相关凭据接口对象。
-
如果 interfaces 为 空,则返回 一个被拒绝的 promise,并带有 "
NotSupportedError"DOMException。 -
遍历 interfaces 中的每个 interface1
-
令 type1 为 interface1 的
[[type]]。 -
遍历 interfaces 中的每个 interface2
-
令 type2 为 interface2 的
[[type]]。 -
如果 type1 等于 type2,则继续。
-
如果 type1 的 同一 get() 请求中允许的类型 不 包含 type2,则返回 一个被拒绝的 promise,并带有 "
NotSupportedError"DOMException。
-
-
-
遍历 interfaces 中的每个 interface
-
如果 options.
mediation为conditional且 interface 不支持conditional用户调解,则返回 一个被拒绝的 promise,并带有TypeError。 -
如果 options.
uiMode为immediate且 interface 不支持immediate用户调解,则返回 一个被拒绝的 promise,并带有TypeError。 -
如果 settings 的 活动凭据类型 包含 interface 的
[[type]],则返回 一个被拒绝的 promise,并带有 "NotAllowedError"DOMException。
-
-
令 origin 为 settings 的 源。
-
令 sameOriginWithAncestors 为:如果 settings 与 其祖先同源,则为
true,否则为false。 -
遍历 options 的 相关凭据接口对象 中的每个 interface
-
如果 permission 为 null,则继续。
-
如果 document 不 被允许使用 permission,则返回 一个被拒绝的 promise,并带有 "
NotAllowedError"DOMException。
-
令 p 为一个新的 promise。
-
在 并行中 运行以下步骤:
-
令 credentials 为 从凭据存储收集
Credentials 的结果,传入 origin、options 和 sameOriginWithAncestors。 -
如果以下所有陈述均为真,则解析 p 并带有 credentials[0],并跳过剩余步骤
-
credentials 的 大小 为 1
-
origin 不 要求用户调解
-
options 是 先验可匹配的。
-
options.
mediation不为 "conditional"。
这可能是一个错误的模型。如果能支持希望同时接受用户名/密码或 webauthn 类型凭据的网站,而无需强迫那些使用前者且希望保持登录状态的用户进行选择,那就太好了。
-
-
令 result 为 请求用户选择一个
Credential的结果,传入 options 和 credentials。 -
如果 result 是一个 接口对象
-
断言:result 为
null或一个Credential。 -
如果 result 是一个
Credential,则 解析 p 并带有 result。 -
如果 result 为
null且 options.mediation不为conditional,则 解析 p 并带有 result。注意:如果 options.
mediation为conditional且发现了一个null凭据,则 promise p 不会解析。
-
-
对 p 做出反应
-
返回 p。
2.5.2. 从凭据存储收集 Credentials
给定一个 源 (origin)、一个 CredentialRequestOptions (options) 以及一个布尔值(当且仅当调用上下文为 与其祖先同源 时为 true,否则为 false)(sameOriginWithAncestors),用户代理可以 从凭据存储收集 Credentials,返回存储在用户代理本地且匹配 options 过滤器的 Credential 对象集合。如果不存在此类已知的 Credential 对象,则返回的集合为空。
-
令 possible matches 为一个空集合。
-
遍历 options 的 相关凭据接口对象 中的每个 interface
-
令 r 为执行 interface 的
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)内部方法的结果,作用于 origin、options 和 sameOriginWithAncestors。如果该操作抛出一个 异常,则重新抛出该异常。 -
断言:r 是一个 接口对象 列表。
-
遍历 r 中的每个 c
-
追加 c 到 possible matches 中。
-
-
-
返回 possible matches。
2.5.3. 存储 Credential
存储 Credential 算法接受一个 Credential (credential),并返回一个 Promise,该 Promise 在对象持久化到 凭据存储 后解析。
-
令 settings 为 当前设置对象。
-
断言:settings 是一个 安全上下文。
-
如果 settings 的 相关全局对象 的 关联文档 不是 完全活动的,则返回 一个被拒绝的 promise,并带有 "
InvalidStateError"DOMException。 -
令 sameOriginWithAncestors 为:如果 当前设置对象 与 其祖先同源,则为
true,否则为false。 -
令 p 为 一个新的 promise。
-
如果 settings 的 活动凭据类型 包含 credential 的
[[type]],则返回 一个被拒绝的 promise,并带有 "NotAllowedError"DOMException。 -
按以下步骤并行运行:
-
对 p 做出反应
-
返回 p。
2.5.4. 创建 Credential
创建 Credential 算法接受一个 CredentialCreationOptions (options),并返回一个 Promise,如果能使用提供的选项创建一个 Credential,则该 Promise 解析为该凭据;否则,如果无法创建,则解析为 null。在特殊情况下,Promise 可能会拒绝并抛出相应的异常。
-
令 settings 为 当前设置对象。
-
断言:settings 是一个 安全上下文。
-
令 global 为 settings 的 全局对象。
-
如果 document 不是 完全活动的,则返回 一个被拒绝的 promise,并带有 "
InvalidStateError"DOMException。 -
令 sameOriginWithAncestors 为:如果 当前设置对象 与 其祖先同源,则为
true,否则为false。 -
如果以下任何陈述为真,则返回 一个被拒绝的 promise,并带有
NotSupportedError -
遍历 interfaces 中的每个 interface
-
如果 permission 为 null,则继续。
-
如果 document 不 被允许使用 permission,则返回 一个被拒绝的 promise,并带有 "
NotAllowedError"DOMException。
-
如果
options.已 中止,则返回 一个被拒绝的 promise,并带有signaloptions.的 中止原因。signal -
令 type 为 interfaces[0] 的
[[type]]。 -
如果 settings 的 活动凭据类型 包含 type,则返回 一个被拒绝的 promise,并带有 "
NotAllowedError"DOMException。 -
令 origin 为 settings 的 源。
-
令 p 为 一个新的 promise。
-
并行运行以下步骤
-
令 r 为执行 interfaces[0] 的
[[Create]](origin, options, sameOriginWithAncestors)内部方法的结果,作用于 origin、options 和 sameOriginWithAncestors。如果该操作抛出一个 异常
-
如果 r 是一个
Credential或null,则 解析 p 并带有 r,并终止这些子步骤。 -
断言:r 是一个算法(如 § 2.2.1.4 [[Create]] internal method 中所定义)。
-
在全局对象 的 DOM 操作任务源 上排队一个任务以运行以下子步骤
-
解析 p,带有 promise 调用 r 的结果,并给定 global。
-
-
-
对 p 做出反应
-
返回 p。
2.5.5. 防止静默访问
防止静默访问 算法接受一个 环境设置对象 (settings),并返回一个 Promise,该 Promise 在 prevent silent access 标志持久化到 凭据存储 后解析。
-
令 origin 为 settings 的 源。
-
如果 settings 的 相关全局对象 的 关联文档 不是 完全活动的,则返回 一个被拒绝的 promise,并带有 "
InvalidStateError"DOMException。 -
令 p 为 一个新的 promise。
-
并行 运行以下步骤
-
返回 p。
3. 密码凭据
无论好坏,许多网站都依赖用户名/密码对作为身份验证机制。PasswordCredential 接口是一种旨在启用此用例的 凭据,它存储用户名和密码,以及可以帮助用户从 凭据选择器 中选择正确账户的元数据。
3.1. 示例
3.1.1. 基于密码的登录
navigator.credentials.get() 从用户的 凭据存储 中获取用户名/密码对。navigator.credentials .get({ 'password': true }) .then(credential => { if (!credential) { // The user either doesn't have credentials for this site, or // refused to share them. Insert some code here to fall back to // a basic login form. return; } if (credential.type == 'password') { var form = new FormData(); form.append('username_field', credential.id); form.append('password_field', credential.password); var opt = { method: 'POST', body: form, credentials: 'include' // Send cookies. }; fetch('https://example.com/loginEndpoint', opt) .then(function (response) { if (/* |response| indicates a successful login */) { // Record that the credential was effective. See note below. navigator.credentials.store(credential); // Notify the user that sign-in succeeded! Do amazing, signed-in things! // Maybe navigate to a landing page via location.href = // '/signed-in-experience'? } else { // Insert some code here to fall back to a basic login form. } }); } });
或者,网站可以直接将凭据数据复制到 form 中,并对该表单调用 submit()。
navigator.credentials .get({ 'password': true }) .then(credential => { if (!credential) { return; // as above... } if (credential.type === 'password') { document.querySelector('input[name=username_field]').value = credential.id; document.querySelector('input[name=password_field]').value = credential.password; document.getElementById('myform').submit(); } });
请注意,前一种方法更受推荐,因为它包含对 store() 的显式调用并保存了凭据。基于 form 的机制依赖于表单提交,这会导航浏览上下文,从而难以确保在登录成功后调用 store()。
注意: 用户代理呈现的 凭据选择器 可能允许用户选择实际上未存储在当前源中的凭据。例如,在登录 https://www.example.com 时,它可能会提供来自 https://m.example.com 的凭据(如 § 6.1 Cross-domain credential access 中所述),或者可能允许用户即时创建新凭据。开发者可以通过在凭据成功使用时每次都调用 store() 来妥善处理这种不确定性,即使是在刚刚从 get() 获取凭据之后:如果凭据尚未为该源存储,用户将获得存储的机会。如果它们已存储,则不会提示用户。
3.1.2. 登录后确认
为了确保在登录成功后向用户提供存储新凭据的建议,可以将它们传递给 store()。
fetch() 将凭据提交到登录端点进行登录,我们可以检查响应以确定用户是否成功登录,并据此通知用户代理。给定如下的登录表单<form action="https://example.com/login" method="POST" id="theForm"> <label for="username">Username</label> <input type="text" id="username" name="username" autocomplete="username"> <label for="password">Password</label> <input type="password" id="password" name="password" autocomplete="current-password"> <input type="submit"> </form>
那么开发者可以使用类似以下处理程序来处理表单提交
document.querySelector('#theForm').addEventListener('submit', e => {
if (window.PasswordCredential) {
e.preventDefault();
// Construct a new PasswordCredential from the HTMLFormElement
// that fired the "submit" event: this will suck up the values of the fields
// labeled with "username" and "current-password" autocomplete
// attributes:
var c = new PasswordCredential(e.target);
// Fetch the form's action URL, passing that new credential object in
// as a FormData object. If the response indicates success, tell the user agent
// so it can ask the user to store the password for future use:
var opt = {
method: 'POST',
body: new FormData(e.target),
credentials: 'include' // Send cookies.
};
fetch(e.target.action, opt).then(r => {
if (/* |r| is a "successful" Response */)
navigator.credentials.store(c);
});
}
});
3.1.3. 更改密码
同样的存储机制可以复用于“更改密码”,无需修改:如果用户更改了其凭据,网站可以通知用户代理他们已使用新凭据成功登录。然后,用户代理可以更新其存储的凭据。
store() 来更新用户的凭据。给定如下的密码更改表单
<form action="https://example.com/changePassword" method="POST" id="theForm"> <input type="hidden" name="username" autocomplete="username" value="user"> <label for="password">New Password</label> <input type="password" id="password" name="password" autocomplete="new-password"> <input type="submit"> </form>
开发者可以使用类似以下的方式来处理表单提交
document.querySelector('#theForm').addEventListener('submit', e => {
if (window.PasswordCredential) {
e.preventDefault();
// Construct a new PasswordCredential from the HTMLFormElement
// that fired the "submit" event: this will suck up the values of the fields
// labeled with "username" and "new-password" autocomplete
// attributes:
var c = new PasswordCredential(e.target);
// Fetch the form's action URL, passing that new credential object in
// as a FormData object. If the response indicates success, tell the user agent
// so it can ask the user to store the password for future use:
var opt = {
method: 'POST',
body: new FormData(e.target),
credentials: 'include' // Send cookies.
};
fetch(e.target.action, opt).then(r => {
if (/* |r| is a "successful" Response */)
navigator.credentials.store(c);
});
}
});
3.2. PasswordCredential 接口
[Exposed =Window ,SecureContext ]interface :PasswordCredential Credential {constructor (HTMLFormElement );form constructor (PasswordCredentialData );data readonly attribute USVString password ; };PasswordCredential includes CredentialUserData ;partial dictionary CredentialRequestOptions {boolean =password false ; };
password, 类型为 USVString,只读-
此属性表示凭据的密码。
[[type]]-
PasswordCredential接口对象 具有一个名为[[type]]的内部槽,其值为 "password"。 [[discovery]]-
The
PasswordCredential接口对象 具有一个名为[[discovery]]的内部槽,其值为 "credential store"。 PasswordCredential(form)-
此构造函数接受一个
HTMLFormElement(form),并运行以下步骤-
令 r 为执行 从 HTMLFormElement 创建 PasswordCredential 的结果,给定 form 和 origin。
-
否则,返回 r。
PasswordCredential(data)-
此构造函数接受一个
PasswordCredentialData(data),并运行以下步骤-
令 r 为执行 从 PasswordCredentialData 创建 PasswordCredential 的结果,作用于 data。
-
否则,返回 r。
-
PasswordCredential 对象可以通过 navigator.credentials.create() 创建,既可以通过显式传递 PasswordCredentialData 字典,也可以基于 HTMLFormElement 的 可提交元素 的内容创建。
dictionary :PasswordCredentialData CredentialData {USVString ;name USVString ;iconURL required USVString ;origin required USVString ; };password typedef (PasswordCredentialData or HTMLFormElement );PasswordCredentialInit partial dictionary CredentialCreationOptions {PasswordCredentialInit ; };password
PasswordCredential 对象是 源绑定 的。
PasswordCredential 的 接口对象 继承了 Credential 对 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 的实现,并定义了其自己的 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、[[Create]](origin, options, sameOriginWithAncestors) 和 [[Store]](credential, sameOriginWithAncestors) 的实现。
3.3. 算法
3.3.1. PasswordCredential 的 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 接收一个 源 (origin)、一个 CredentialRequestOptions (options) 以及一个布尔值(当且仅当调用上下文为 与其祖先同源 时为 true,否则为 false)(sameOriginWithAncestors) 被调用。该算法返回来自 凭据存储 的 Credential 对象集合。如果没有可用的匹配 Credential 对象,则返回的集合为空。
如果 sameOriginWithAncestors 不为 true,算法将抛出 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则抛出 "NotAllowedError"DOMException。注意: 此限制旨在解决 § 6.4 Origin Confusion 中提出的顾虑。
-
如果 options["
password"] 不为true,则返回空集合。 -
-
该凭据是一个
PasswordCredential -
该凭据的
[[origin]]与 origin 为 同源。
-
3.3.2. PasswordCredential 的 [[Create]](origin, options, sameOriginWithAncestors)
[[Create]](origin, options, sameOriginWithAncestors) 接收一个 源 (origin)、一个 CredentialCreationOptions (options) 以及一个布尔值(当且仅当调用上下文为 与其祖先同源 时为 true,否则为 false)(sameOriginWithAncestors) 被调用。该算法返回一个 PasswordCredential,如果能创建;否则返回 null。CredentialCreationOptions 字典必须包含一个 password 成员,该成员持有一个 HTMLFormElement 或一个 PasswordCredentialData。如果该成员的值不能用于创建 PasswordCredential,此算法将抛出一个 TypeError 异常。
-
如果 options["
password"] 是一个HTMLFormElement,则返回执行 从 HTMLFormElement 创建 PasswordCredential 的结果,给定 options["password"] 和 origin。重新抛出任何异常。 -
如果 options["
password"] 是一个PasswordCredentialData,则返回执行 从 PasswordCredentialData 创建 PasswordCredential 的结果,给定 options["password"]。重新抛出任何异常。
3.3.3. PasswordCredential 的 [[Store]](credential, sameOriginWithAncestors)
[[Store]](credential, sameOriginWithAncestors) 接收一个 PasswordCredential (credential) 以及一个布尔值(当且仅当调用上下文为 与其祖先同源 时为 true,否则为 false)(sameOriginWithAncestors) 被调用。该算法在 credential 持久化到 凭据存储 后返回 undefined。
如果 sameOriginWithAncestors 不为 true,算法将返回 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则抛出 "NotAllowedError"DOMException,且不修改用户代理的 凭据存储。注意: 此限制旨在解决 § 6.4 Origin Confusion 中提出的顾虑。
-
如果用户代理的 凭据存储 包含一个
PasswordCredential(stored),其id属性为 credential 的id,且其[[origin]]槽与 credential 的[[origin]]为 同源,则-
如果用户授予了更新凭据的权限(如定义 用户调解 时所讨论),则
否则,如果用户授予了存储凭据的权限(如定义 用户调解 时所讨论),则
-
将
PasswordCredential存储在 凭据存储 中,并具有以下属性id-
credential 的
id name,-
credential 的
name iconURL-
credential 的
iconURL [[origin]]-
credential 的
[[origin]] 密码-
credential 的
password
-
3.3.4. 从 HTMLFormElement 创建 PasswordCredential
要 从 HTMLFormElement 创建 PasswordCredential,给定一个 HTMLFormElement (form) 和一个 源 (origin),运行这些步骤。
注意: § 3.1.2 Post-sign-in Confirmation 和 § 3.1.3 Change Password 提供了预期用法的示例。
-
令 data 为一个新的
PasswordCredentialData字典。 -
将 data 的
origin成员的值设置为 origin 的值。 -
令 formData 为在 form 上执行
FormData构造函数的结果。 -
令 newPasswordObserved 为
false。 -
遍历 elements 中的每个 field,运行以下步骤
-
如果 field 没有
autocomplete属性,则跳至下一个 field。 -
令 name 为 field 的
name属性的值。 -
如果 formData 的
has()方法在 name 上执行时返回false,则跳至下一个 field。 -
如果 field 的
autocomplete属性的值包含一个或多个 自动填充详细标记 (tokens),则-
遍历 tokens 中的每个 token
-
如果 token 是以下字符串之一的 ASCII 大小写不敏感 匹配项,则运行关联步骤
- "
new-password" -
将 data 的
password成员的值设置为在 name 上执行 formData 的get()方法的结果,并将 newPasswordObserved 设置为true。 - "
current-password" -
如果 newPasswordObserved 为
false,则将 data 的password成员的值设置为在 name 上执行 formData 的get()方法的结果。注意: 通过检查 newPasswordObserved 是否为
false,new-password字段优先于current-password字段。 - "
photo" - "
name" - "
nickname" - "
username"
- "
-
-
-
-
令 c 为执行 从 PasswordCredentialData 创建 PasswordCredential 的结果,作用于 data。如果该操作抛出一个 异常,则重新抛出该异常。
-
断言:c 是一个
PasswordCredential。 -
返回 c。
3.3.5. 从 PasswordCredentialData 创建 PasswordCredential
要 从 PasswordCredentialData 创建 PasswordCredential,给定一个 PasswordCredentialData (data),运行这些步骤。
-
令 c 为一个新的
PasswordCredential对象。 -
设置 c 的属性如下
-
返回 c。
3.3.6. PasswordCredential 的 CredentialRequestOptions 匹配
给定一个 CredentialRequestOptions (options),如果 PasswordCredential 应作为 get() 请求的响应可用,则以下算法返回 "Matches",否则返回 "Does Not Match"。
-
如果 options 具有一个其值为
true的password成员,则返回 "Matches"。 -
返回 "
Does Not Match"。
4. 联合凭据
4.1. FederatedCredential 接口
[Exposed =Window ,SecureContext ]interface :FederatedCredential Credential {constructor (FederatedCredentialInit );data readonly attribute USVString provider ;readonly attribute DOMString ?protocol ; };FederatedCredential includes CredentialUserData ;dictionary {FederatedCredentialRequestOptions sequence <USVString >;providers sequence <DOMString >; };protocols partial dictionary CredentialRequestOptions {FederatedCredentialRequestOptions ; };federated
provider, 类型为 USVString,只读-
该凭据的联合身份提供商。关于有效格式的详细信息,请参阅 § 4.1.1 Identifying Providers。
protocol, 类型为 DOMString,只读,可为空-
凭据的联合身份提供商协议(例如“
openidconnect”)。如果值为null,则可以从provider中推断出协议。 [[type]]-
FederatedCredential接口对象有一个名为[[type]]的内部槽,其值为 "federated"。 [[discovery]]-
FederatedCredential接口对象有一个名为[[discovery]]的内部槽,其值为 "credential store"(凭据存储)。 FederatedCredential(data)-
此构造函数接受一个
FederatedCredentialInit(data),并执行以下步骤-
令 r 为在 data 上执行 从 FederatedCredentialInit 创建 FederatedCredential 的结果。如果抛出了 异常,则重新抛出该异常。
-
返回 r。
-
FederatedCredential 对象可以通过将 FederatedCredentialInit 字典传递给 navigator.credentials.create() 来创建。
dictionary :FederatedCredentialInit CredentialData {USVString ;name USVString ;iconURL required USVString ;origin required USVString ;provider DOMString ; };protocol partial dictionary CredentialCreationOptions {FederatedCredentialInit ; };federated
FederatedCredential 对象是 源绑定的。
FederatedCredential 的 接口对象继承了 Credential 对 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 的实现,并定义了其自身对 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、[[Create]](origin, options, sameOriginWithAncestors) 和 [[Store]](credential, sameOriginWithAncestors) 的实现。
注意:如果将来我们让用户代理代表用户获取身份验证令牌,我们可以通过构建 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 的实现来实现。
4.1.1. 识别提供商
每个站点在引用特定的联合身份提供商时,都应该使用相同的标识符。例如,Facebook 登录不应被称为“Facebook”、“Facebook 登录”、“FB”、“FBL”、“Facebook.com”等。它应该有一个每个人都可以使用的规范标识符,因为一致的识别使用户代理能够提供帮助。
为了保持一致性,传入本文档定义的 API(例如 FederatedCredentialRequestOptions 的 providers 数组,或 FederatedCredential 的 provider 属性)的联合,必须通过提供商用于登录的源的 ASCII 序列化进行标识。也就是说,Facebook 应表示为 https://#,Google 应表示为 https://#。
这种源的序列化不包括末尾的 U+002F 斜杠("/"),但用户代理应该静默地接受它们:https://#/ 显然被视为与 https://# 相同。
4.2. 算法
4.2.1. FederatedCredential 的 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 使用源(origin)、CredentialRequestOptions(options)以及一个布尔值(当且仅当调用上下文与其祖先同源时为 true)来调用(sameOriginWithAncestors)。该算法从凭据存储中返回一组 Credential 对象。如果没有匹配的 Credential 对象可用,则返回的集合为空。
-
如果 sameOriginWithAncestors 为
false,则抛出 "NotAllowedError"DOMException。注意: 此限制旨在解决 § 6.4 Origin Confusion 中提出的顾虑。
-
如果 options["
federated"] 不为true,则返回空集。
4.2.2. FederatedCredential 的 [[Create]](origin, options, sameOriginWithAncestors)
[[Create]](origin, options, sameOriginWithAncestors) 使用源(origin)、CredentialCreationOptions(options)以及一个布尔值(当且仅当调用上下文与其祖先同源时为 true)来调用(sameOriginWithAncestors)。该算法返回一个 FederatedCredential(如果可以创建),否则返回 null,或者在特殊情况下抛出异常。
-
返回给定 options["
federated"] 执行 从 FederatedCredentialInit 创建 FederatedCredential 的结果。如果该操作抛出了异常,则重新抛出该异常。
4.2.3. FederatedCredential 的 [[Store]](credential, sameOriginWithAncestors)
[[Store]](credential, sameOriginWithAncestors) 使用 FederatedCredential(credential)和一个布尔值(当且仅当调用上下文与其祖先同源时为 true)来调用(sameOriginWithAncestors)。一旦 credential 被持久化到凭据存储,该算法将返回 undefined。
如果 sameOriginWithAncestors 不为 true,算法将返回 NotAllowedError。
-
如果 sameOriginWithAncestors 为
false,则抛出 "NotAllowedError"DOMException,且不更改用户代理的凭据存储。注意: 此限制旨在解决 § 6.4 Origin Confusion 中提出的顾虑。
-
如果用户代理的凭据存储包含一个
FederatedCredential,其id属性为 credential 的id,且其[[origin]]槽与 credential 的[[origin]]同源,且其provider为 credential 的provider,则返回。 -
如果用户授予了存储凭据的权限(如在定义用户中介时所述),则将
FederatedCredential存储到凭据存储中,并具有以下属性:
4.2.4. 从 FederatedCredentialInit 创建 FederatedCredential
为了从 FederatedCredentialInit 创建 FederatedCredential,给定一个 FederatedCredentialInit (init),执行以下步骤。
5. 用户中介
通过 API 将凭据信息暴露给 Web 对用户隐私有许多潜在影响。因此,用户代理必须在许多情况下让用户参与,以确保他们清楚地了解正在发生的事情,以及他们的凭据正在与谁共享。
如果某个特定操作是在获得用户的明确同意后进行的,我们称之为用户中介。例如,同意可以通过用户与凭据选择器界面的直接交互来表达。通常,用户中介的操作将涉及向用户呈现某种 UI,并要求他们做出决定。
如果操作是在没有明确用户同意的情况下静默进行的,则该操作是未中介的。例如,如果用户将浏览器配置为向特定来源授予持久性凭据访问权限,则无需向用户呈现要求做出决定的 UI 即可提供凭据。
在此,我们将阐述适用于所有凭据类型的一些要求,但请注意,用户代理(处于协助用户的特权位置)仍有很大的余地。此外,特定的凭据类型可能具有不同于此处更通用要求的特定要求。
5.1. 存储和更新凭据
凭据信息是敏感数据,用户必须保持对该信息存储的控制。例如,无意中的凭据存储可能会意外地将用户在特定设备上的本地配置文件与特定的在线身份链接起来。为了降低这种风险
-
在没有用户中介的情况下,不应存储或更新凭据信息。例如,用户代理可以在每次调用
store()时向用户显示“保存此凭据?”对话框。如果用户代理选择以“始终保存密码”选项的形式提供持久的同意授权,则可以推断出用户同意(尽管我们建议用户代理应倾向于更窄范围的选项:例如“始终保存_生成的_密码”,或“始终保存此站点的密码”)。
-
当存储凭据时,用户代理应通知用户。这可以采取地址栏中的图标或类似位置的形式。
-
用户代理必须允许用户手动删除已存储的凭据。此功能可以实现为设置页面,或通过与上述通知的交互来实现。
5.2. 要求用户中介
默认情况下,所有源都需要用户中介,因为凭据存储中的相关防止静默访问标志被设置为 true。用户可以选择向来源授予持久的凭据访问权限(例如以“保持登录此站点”选项的形式),这将把此标志设置为 false。在这种情况下,用户将始终登录到该站点,这从可用性和便利性的角度来看是可取的,但可能会产生意想不到的后果(例如,考虑一个在设备间同步此标志状态的用户代理)。
为了降低这种风险
-
用户代理必须允许用户为特定来源或所有来源要求用户中介。此功能可以实现为覆盖每个来源的防止静默访问标志并将其返回为
false的全局开关,或者通过针对特定来源(或特定来源上的特定凭据)的更细粒度的设置来实现。 -
在没有用户中介的情况下,用户代理不得将源的防止静默访问标志设置为
false。例如,§ 5.3 凭据选择中描述的凭据选择器可以包含一个复选框,用户可以切换该复选框以将凭据标记为在来源无需中介即可使用,或者用户代理可以为其凭据管理器设置一个引导流程,询问用户的默认设置。 -
当向来源提供凭据时,用户代理必须通知用户。这可以采取地址栏中的图标或类似位置的形式。
-
如果用户清除了来源的浏览数据(cookie、localStorage 等),用户代理必须将该来源的防止静默访问标志设置为
true。
5.3. 凭据选择
在响应需要用户中介的来源上的 get() 调用时,用户代理必须请求用户共享凭据信息的许可。这应采取凭据选择器的形式,该选择器向用户展示可在站点上使用的凭据列表,允许他们选择一个提供给网站,或完全中止请求。
选择器的用户界面应以与网站可以生成的 UI 区分开来的方式实现。例如,选择器可能会以某种无法伪造的方式覆盖用户代理的 UI。
选择器的用户界面必须包含请求凭据的来源的指示。
选择器的用户界面应包含与请求凭据的来源关联的所有 Credential 对象。
为了增强此类选择器的效用,用户代理可以在内部将除本文档中指定的属性之外的信息与每个 Credential 对象关联起来。例如,网站图标可以帮助区分身份提供商等。存储的任何附加信息不得直接暴露给 Web。
选择器的行为在此处未定义:鼓励用户代理尝试各种 UI 处理方式,以教育用户了解其身份验证选项,并引导他们完成选择凭据的过程。话虽如此,选择器的接口如下
CredentialRequestOptions (options) 以及来自凭据存储的一组 Credential 对象(本地发现的凭据),用户代理可以请求用户选择凭据。如果用户选择不与站点共享凭据,此算法将返回 null;如果用户选择了特定凭据,则返回 Credential 对象;如果用户选择了凭据类型,则返回 Credential 接口对象。
6. 安全注意事项
以下部分代表了各种安全和隐私考虑的指南。个别凭据类型可以执行这些指南的更严格或更宽松的版本。
6.1. 跨域凭据访问
凭据是敏感信息,用户代理在确定何时可以安全地与网站共享凭据时需要谨慎行事。最安全的选择是将凭据共享限制在保存它们的精确来源上。然而,这对 Web 来说可能太严格了:考虑将功能划分为子域的站点,如 example.com 与 admin.example.com。
作为不干扰用户和保护其凭据之间的妥协,用户代理
-
不得在方案组件代表安全性降低的来源之间共享凭据。也就是说,允许在
http://example.com/上保存的凭据可用于https://example.com/(以鼓励开发人员迁移到安全传输)可能是合理的,但反之则很危险。 -
可以使用公共后缀列表 [PSL],通过比较凭据的
[[origin]]的可注册域与调用get()所在的来源,来确定凭据的有效范围。也就是说:在https://admin.example.com/和https://example.com/上保存的凭据,当从https://www.example.com/调用get()时,可能会提供给用户,反之亦然。 -
如果凭据的来源与调用来源不完全匹配,在没有用户中介的情况下,不得响应
get()向来源提供凭据。也就是说,https://example.com的Credential对象不会直接返回给https://www.example.com,但可以通过选择器提供给用户。
6.2. 凭据泄露
我们建议开发人员采取一些预防措施,通过设置合理的限制数据发送端点的内容安全策略 [CSP],来降低跨站脚本攻击可能导致用户帐户被持久访问的风险。特别是,开发人员应确保在页面的策略中显式或隐式设置以下指令
-
script-src 和 object-src 都限制了页面上的脚本执行,使得跨站脚本攻击成功的可能性降低。如果站点正在填充
form元素,也应设置 form-action 指令。 -
connect-src 限制了
fetch()可以提交数据的来源(这降低了凭据被泄露到evil.com的风险)。 -
child-src 限制了页面中可以嵌入的嵌套浏览上下文,使得注入恶意
postMessage()目标更加困难。[HTML]
当然,开发人员还应适当地转义输入和输出,并考虑使用其他防御层,例如子资源完整性 [SRI],以进一步降低风险。
在定义特定凭据类型时,特定凭据类型应适当考虑凭据数据在网上传输的方式。例如,定义仅限于同源端点的传输机制可能是合理的。
6.3. 不安全站点
用户代理不得将此处定义的 API 暴露给非安全上下文的环境。用户代理可能会实现自动填充机制,将用户凭据存储并填充到非潜在可信 URL 上的登录表单中,但这些站点不能被信任与凭据管理器进行任何有意义的交互,并且这些站点不得访问存储在安全上下文中的凭据。
6.4. 来源混淆
如果框架页面可以访问此处定义的 API,则可能会诱导用户授予对除顶级浏览上下文以外的来源的凭据访问权限,而这是用户唯一能够合理理解的安全来源。
本文档将凭据管理 API 暴露给这些上下文,因为如果用户代理投入足够的思考并将上下文置于其 UI 中,某些凭据类型可能很容易实现。
然而,特定的凭据类型在这些上下文中暴露将会有风险。这些凭据类型通过其 [[Create]](origin, options, sameOriginWithAncestors)、[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 和 [[Store]](credential, sameOriginWithAncestors) 方法中的检查进行限制(视情况而定)。
例如,PasswordCredential 的 [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 方法如果在 Worker 或非顶级浏览上下文中调用,将立即返回空集。
6.5. 登出
如果用户已选择自动登录网站(如§ 5.2 要求用户中介中所述),则用户代理将在来源请求时随时向其提供凭据。网站可以通过调用 CredentialsContainer 的 preventSilentAccess() 方法来指示用户代理抑制此行为,这将关闭特定来源的自动登录。
用户代理依赖于网站执行正确的操作;一个疏忽(或恶意)的网站可能只是忽略了调用此方法,导致用户代理继续提供凭据,违背了用户的明显意图。这比用户点击“登出”时站点未清除用户凭据的现状稍微糟糕一些,因为用户代理成为了身份验证的共犯。
用户必须对此行为有一定的控制权。如§ 5.2 要求用户中介中所述,清除来源的 cookie 也将重置该来源的防止静默访问标志(在凭据存储中)为 true。此外,用户代理应提供某种 UI 辅助功能,以禁用特定来源的自动登录。例如,这可以与向来源提供凭据的通知挂钩。
7. 隐私考虑
7.1. 时间攻击
如果用户没有某个来源的凭据,则对 get() 的调用将非常快速地完成。恶意网站可以区分没有凭据的用户和有凭据但选择不共享它们的用户。
用户代理还应限制凭据请求的频率。页面在短时间内请求多次凭据几乎肯定是滥用行为。
7.2. 选择器泄露
如果用户代理的凭据选择器显示由来源提供的图像(例如,如果 Credential 显示网站的图标),那么请求这些图像时不得直接与实例化选择器绑定,以避免泄露选择器的使用情况。一种选择是在保存或更新 Credential 时在后台获取图像,并将其缓存,生命周期与 Credential 相同。
必须使用凭据模式设置为 "omit"、服务工作线程模式设置为 "none"、客户端设置为 null、发起者设置为空字符串,以及目标设置为 "subresource" 来获取这些图像。
此外,如果用户代理允许用户更改与凭据关联的名称或图标,则对数据的更改不应暴露给网站(例如,考虑一个用户将某个来源的两个凭据命名为“我的假帐户”和“我的真帐户”的情况)。
7.3. 本地存储的数据
此 API 为源提供了与用户配置文件一起持久存储数据的能力。由于大多数用户代理对待凭据数据的方式与“浏览数据”(cookie 等)不同,这可能会产生令用户意外的副作用,因为他们可能认为在清除 cookie 时已经擦除了来源的所有痕迹。
用户代理应提供 UI,向用户明确说明为来源存储了凭据数据,并应方便用户在不再需要保留数据时删除此类数据。
8. 实现考虑
本节是非规范性的。
8.1. 网站作者
在此处添加关于何时以及如何使用该 API 的想法,特别是关于 mediation。[w3c/webappsec Issue #290]
描述通过 fetch() 结合 FormData 正文提交凭据的编码限制。
当为给定的凭据类型执行特性检测时,建议开发人员验证相关的 Credential 专业化是否存在,而不是依赖于 navigator.credentials 的存在。后者验证 API 本身的存在,但不能确保站点所需的特定类型的凭据受支持。例如,如果站点需要密码,则检查 if (window.PasswordCredential) 是最有效的支持验证。
8.2. 扩展点
本文档提供了一个通用的、高级的 API,旨在通过特定的凭据类型进行扩展,以满足特定的身份验证需求。这样做希望是简单的
-
定义一个继承自
Credential的新接口 -
在
ExampleCredential的接口对象上定义适当的[[Create]](origin, options, sameOriginWithAncestors)、[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)、[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)和[[Store]](credential, sameOriginWithAncestors)方法。[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)适用于永远有效且因此可以直接从凭据存储中复制出的凭据,而[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)适用于需要从凭据源重新生成的凭据。建议像
PublicKeyCredential的[[Create]](origin, options, sameOriginWithAncestors)和[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)操作中的长运行操作使用options.signal以允许开发人员中止该操作。有关详细说明,请参阅 DOM § 3.3 在 API 中使用 AbortController 和 AbortSignal 对象。ExampleCredential的[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)内部方法使用源(origin)、CredentialRequestOptions 对象(options)以及一个布尔值(当且仅当调用上下文与其祖先同源时为true)来调用。该算法返回匹配所提供选项的一组Credential对象。如果没有匹配的Credential对象可用,则返回的集合为空。-
断言:
options[example] 存在。 -
如果
options[example] 不为真,则返回空集。 -
对于凭据存储中的每个凭据
-
...
-
-
-
定义
ExampleCredential接口对象的[[discovery]]槽的值 -
用新凭据类型响应
get()所需的选项扩展CredentialRequestOptions -
用新凭据类型在响应
create()时创建Credential对象所需的数据扩展CredentialCreationOptions -
如果新凭据类型支持
conditional用户中介,则定义ExampleCredential/isConditionalMediationAvailable()以返回以true决议的 Promise。 -
按照 § 2.1.2.1 注册条目要求和更新流程中的程序,为新的 "example" 凭据类型及其对应的条目添加到凭据类型注册表
-
CredentialCreationOptions' 和CredentialRequestOptions' 选项成员标识符(本例中为 "example"),以及
注意:凭据类型选项字典的选项成员标识符在
CredentialCreationOptions和CredentialRequestOptions中必须相同,并且应与Credential接口对象的[[type]]槽中的凭据类型值相同。 -
您可能还会发现新的原语是必要的。例如,您可能希望在某种复杂的、多因素登录过程中返回许多 Credential 对象,而不是只有一个。这可以通过向 CredentialsContainer 添加一个返回 sequence<Credential> 的 getAll() 方法,并定义处理请求不同类型凭据的合理机制,以通用方式实现。
对于任何此类扩展,我们建议咨询 public-webappsec@ 以获得咨询和审查。
8.3. 浏览器扩展
理想情况下,实现某种扩展系统的用户代理将允许第三方挂接到这些 API 端点,以便以用户代理可以通过这种指令性方法改进自身相同的方式,改进第三方凭据管理软件的行为。
这可以从用户代理中介的复杂新 API,到仅仅允许扩展覆盖 get() 和 store() 端点以用于其自身目的,范围各不相同。
9. 未来工作
本节是非规范性的。
此处定义的 API 仅做了最低限度的努力来将用户代理的凭据管理器暴露给 Web,并允许 Web 帮助这些凭据管理器理解何时使用了联合身份提供商。下一个合乎逻辑的步骤将沿着类似 [WEB-LOGIN](以及在某种程度上 Mozilla 的 BrowserID [BROWSERID])文档中勾勒的方向进行。
用户代理处于能够有效中介用户、身份提供商和网站之间关系的独特位置。如果用户代理能够消除与典型身份验证流程相关的一些风险和混乱,用户将处于比今天好得多的位置。
暴露此信息的一种自然方式可能是扩展 FederatedCredential 接口,添加身份验证令牌等属性,并可能添加某种声明提供商支持的身份验证类型的清单格式属性。
此处描述的 API 旨在具有足够的扩展性,以支持需要用户交互的用例,例如与请求凭据的网站以外的网站交互。我们希望我们所确定的基于 Promise 的系统足够可扩展,以支持这些类型的异步流(可能需要多个浏览上下文之间的某种交互,例如 idp.com 上的中介活动可能会决议返回给 rp.com 的 Promise)或设备与用户代理之间(例如 [WEBAUTHN])在未来交互,而无需从头重新设计 API。
小步快跑。
