Credential Management API(凭据管理接口)
发布于 更新于
一句话: 根据 MDN 的说明,Credential Management API “使网站能够创建、存储和获取
凭据”(“enables a website to create, store, and retrieve credentials”),核心接口是
CredentialsContainer,通过 navigator.credentials 暴露,并且只在安全上下文
(HTTPS)中可用。
MDN 将凭据描述为“使系统能够做出身份验证决定的项目”——用户向网站出示的、用以证明
自己身份的凭证。核心接口 CredentialsContainer 提供三个主要方法:create()
(创建新凭据)、store()(在本地存储新凭据)以及 get()(获取凭据以登录用户)。
CredentialsContainer 的参考文档在这三个方法之外还列出了第四个实例方法,而它恰恰
最容易被忽略:preventSilentAccess()「设置一个标志,用于指定后续访问当前源时是否
允许自动登录,然后返回一个空的 Promise」。这四个方法都仅在安全上下文中可用。
这三个主要方法最好按它们各自 resolve 出什么来理解,因为形态并不相同:
create()以依据所给选项创建的新Credential实例 resolve——或者在无法创建 任何Credential对象时以nullresolve。get()以匹配所给参数的Credential实例 resolve。如果无法无歧义地取得单一 凭据,则以nullresolve。store()把一组用户凭据存入所提供的Credential实例,并在Promise中返回 该实例。
根据 MDN,该 API 支持四种凭据类型,均为 Credential 的子类:
- 密码 ——
PasswordCredential - 联合身份 ——
IdentityCredential(以及已弃用的FederatedCredential) - 一次性密码(OTP) ——
OTPCredential - Web Authentication ——
PublicKeyCredential
本页仅涵盖 CredentialsContainer 的通用能力;关于 WebAuthn/passkey 这一具体凭据
类型,请参阅专门的
WebAuthn 与 passkey 参考文档。
根据 MDN 针对 CredentialsContainer 的 browser-compat-data,该接口本身从 Chrome
51、Edge 18、Firefox 60 与 Safari 13 开始支持。具体方法的支持时间可能晚于接口
本身,因此应针对你实际需要的方法查阅对应的兼容性数据,而不要假设整个接口一次性
全部就绪。BCD 对同一接口的记录是:
| 方法 | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
CredentialsContainer |
51 | 18 | 60 | 13 |
get() |
51 | 18 | 60 | 13 |
store() |
51 | mirror |
60 | 13 |
create() |
60 | 18 | 60 | 13 |
preventSilentAccess() |
60 | 18 | 60 | 17(13 为部分实现) |
store() 的 Edge 单元格写的是 mirror,因为 BCD 在那里记录的就是这个值:另外三个方法
各自带有一条显式的 Edge 记录(18),而 store() 没有自己的 Edge 数字,而是镜像对应的
Chromium 数据。
真正会咬人的是 preventSilentAccess() 的 Safari 单元格,因为它的故障模式并不是「方法不
存在」。BCD 为该方法记录了两条 Safari 条目:从 17 起完整支持,以及一条覆盖 Safari 13 到
16 的 partial_implementation,其说明写的是「该方法存在,但总是以 NotSupportedError
异常拒绝」。因此在这些版本上,typeof navigator.credentials.preventSilentAccess === 'function' 这类检查会通过,调用却照样失败——方法确实存在,而且永久性损坏。要检测它,靠的
是捕获调用抛出的 NotSupportedError,而不是探测属性是否存在。
真正最容易让实际实现翻车的缺口,在更下一层——在凭据类型而不是容器上。MDN 把
PasswordCredential 标为有限可用(limited availability):「该特性不属于
Baseline,因为它在一些使用最广泛的浏览器中并不工作。」BCD 则说明了具体是哪些:它
记录 PasswordCredential 自 Chrome 51 起支持,而在 Firefox 与 Safari 中记录为
不支持,两者给出的都是一个尚未关闭的实现缺陷链接,而不是版本号。因此
'credentials' in navigator 为真,并不能说明你可以构造 PasswordCredential:这两个
引擎提供了容器,却没有提供这种类型。
有三个时刻值得关注:登录成功后存储、再次访问时取回,以及登出时清除自动登录标志。
// 1. 在成功登录后存储一个密码凭据。async function rememberPasswordCredential({ id, password, name, iconURL }) { if (!('PasswordCredential' in window)) return; const credential = new PasswordCredential({ id, password, name, iconURL }); await navigator.credentials.store(credential);}PasswordCredential 以只读属性暴露 password、name(一个人类可读的字符串,提供
用于在凭据选择器中展示的公开名称)与 iconURL(指向一张图标图片的 URL),此外还有
它从 Credential 继承来的 id 与 type。
// 2. 页面加载时尝试静默登录。const credential = await navigator.credentials.get({ password: true, mediation: 'silent',});if (credential) { await signInWith(credential);} else { // 没有任何凭据能在不询问用户的前提下交出——那就展示登录按钮, // 而不是弹出一个用户并没有要求的对话框。 showLoginButton();}// 3. 登出时,阻止浏览器把用户直接再登回去。async function signOut() { await serverSignOut(); if (!navigator.credentials?.preventSilentAccess) return; try { await navigator.credentials.preventSilentAccess(); } catch (err) { // Safari 13–16 提供了该方法,但总是以 NotSupportedError 拒绝。 // 上面的属性检查看不出这一点,所以这里只吸收这一种拒绝, // 并退回到清理你自己的会话状态。 if (err.name !== 'NotSupportedError') throw err; forgetLocalSession(); }}这个 try/catch 会处理 Safari 13–16 的 NotSupportedError,因为存在性检查无法区分一个
可用的方法和 BCD 记录的那种部分实现。注意这里只吞掉了 NotSupportedError——其他异常仍会
继续抛出,真正的故障不会被藏起来。
MDN 给出的正是这个理由:「你可以在用户登出网站之后调用它,以确保他们在下次访问站点
时不会被自动登录。」该方法以 undefined resolve。
选择让用户参与到什么程度
Section titled “选择让用户参与到什么程度”get() 接受一个 mediation 选项——一个字符串,表示在取回凭据的过程中用户以何种方式
参与。默认值是 "optional"。根据 MDN,四个取值分别是:
"silent"—— 不会要求用户进行身份验证;用户代理会在可能的情况下自动重新验证并 登录用户,而如果需要用户同意,该 promise 会以null兑现。适用于希望用户一进入 应用就尽可能自动登录,但在做不到时也不要弹出令人困惑的登录对话框的场景。"optional"—— 如果凭据能在无需用户介入的情况下交出,就直接交出,从而实现自动 重新验证;如果需要用户介入,用户代理则会要求用户进行身份验证。适用于你有合理把握 用户不会因为看到登录对话框而感到意外的场景——例如用户刚点击了「登录/注册」按钮。"required"—— 总是要求用户进行身份验证。适用于你想强制身份验证的场景,例如在 执行敏感操作前要求重新验证(MDN 给的例子是确认一笔信用卡付款),或在切换用户时。"conditional"—— 已发现的凭据会以非模态对话框的形式呈现给用户,同时标明请求 凭据的来源。实践中这意味着对可用凭据进行自动填充。
get() 另外两个值得了解的选项:password 是那个布尔值,用于请求浏览器以
PasswordCredential 形式取回已存储的密码;federated 接受
{ protocols, providers }——但 MDN 指出 FederatedCredential「现已被取代,开发者
应优先使用 identity 选项(如果可用)」。
给请求设定一个期限
Section titled “给请求设定一个期限”get() 接受 signal——一个 AbortSignal,可用于中止一个正在进行的请求。被中止的
操作可能仍正常完成(通常是因为中止发生在操作已经结束之后),也可能以该 signal 的
reason 拒绝,默认是一个 AbortError DOMException。把它与 AbortSignal.timeout()
搭配,就能把一个无人应答的提示变成可捕获的错误:
try { const credential = await navigator.credentials.get({ password: true, signal: AbortSignal.timeout(10_000), }); // …} catch (err) { if (err.name === 'TimeoutError') return showLoginButton(); if (err.name === 'AbortError') return; // 请求被取消 throw err;}因 AbortSignal.timeout() 设定的超时而被自动中止的请求,拒绝时给出的是
TimeoutError 而不是 AbortError——如果你用了期限,请对两者都做分支处理。
如何在运行时检测
Section titled “如何在运行时检测”在调用之前先对 navigator.credentials 做特性检测,因为在不支持该 API 的浏览器
或上下文(例如非安全源)中,它是未定义的。又因为容器可能存在、而你需要的凭据类型
并不存在,所以两者都要检测:
async function getStoredCredential() { if (!('credentials' in navigator) || !('PasswordCredential' in window)) { // 要么此处无法使用 Credential Management API(浏览器不支持,或处于 // 非安全上下文),要么该引擎只提供了容器而没有密码凭据—— // 回退到手动登录表单。 return null; } return navigator.credentials.get({ password: true, mediation: 'silent' });}- 根据 MDN,该 API 仅在安全上下文(HTTPS)中可用——在依赖它之前,请确认页面是 通过安全方式提供的。
- 不要把
'credentials' in navigator当作密码凭据可用的证据:BCD 记录PasswordCredential在 Firefox 与 Safari 中不受支持,而这两者都提供了CredentialsContainer。要检测的是凭据类型,而不只是容器。 - 把 resolve 出
null当作正常结果而非错误:无法无歧义取得单一凭据时get()以nullresolve;需要用户同意时mediation: 'silent'以null兑现;无法创建任何Credential对象时create()以nullresolve。 - MDN 在四种凭据类型中将
FederatedCredential标记为已弃用,并指出federated选项已被identity选项取代(在可用的情况下)——在基于它构建新的登录流程之前, 请查阅 MDN 上的最新状态。 - 各方法的支持可能滞后于接口本身:BCD 把
create()与preventSilentAccess()记在 Chrome 60,而容器本身是 Chrome 51;preventSilentAccess()记在 Safari 17,而容器 是 Safari 13。请针对你实际需要的方法逐一确认。 - 不要用检查属性的方式来对
preventSilentAccess()做特性检测。BCD 把 Safari 13–16 记录 为部分实现:方法存在,但总是以NotSupportedError拒绝,所以检查会通过而调用照样失败。 要把调用包起来,捕获NotSupportedError后退回到清理你自己的会话,并让其他异常继续抛出。 - 在登出时调用
preventSilentAccess(),而不是在登录时——它正是那个阻止下次访问把 用户直接登回去的标志位。注意对于PublicKeyCredential,它通常不起作用,因为这类 验证器通常本就需要用户交互。 - 如果你在读更老的代码或更老的文章:在早期版本的规范中,
preventSilentAccess()叫requireUserMediation()。 - 对
get()的各类拒绝分别处理:NotAllowedError覆盖用户取消请求、调用被identity-credentials-get、publickey-credentials-get或otp-credentials权限 策略阻止,以及调用方来源是不透明源(opaque origin);SecurityError表示调用方 域名不是有效域名;AbortError与TimeoutError来自signal选项。 - 对于
PublicKeyCredential(WebAuthn/passkey)这一具体类型,请遵循下方专门的 WebAuthn 参考文档,而不要将其当作通用凭据处理。
- WebAuthn 与 passkey ——
PublicKeyCredential类型的深入说明,也是本页唯一留给专门文档的凭据类型。 - Web 能力索引 —— PWA 可以依托的其他浏览器能力。
- Payment Request API —— MDN 为
mediation: "required"举例时所说的那类敏感操作。 - Web app manifest id:稳定的 PWA 身份标识 —— 已安装应用自身的身份标识,与用户的身份相对。