跳转到内容

能力 · API

Credential Management API

发布于 更新于

Credential Management API 就是 navigator.credentials 这个容器:页面通过它向浏览器的凭据存储索取一个 Credential、交回一个让浏览器保存、新建一个,或者告诉浏览器不要再静默登录。五种凭据类型接入这个容器:PasswordCredential、PublicKeyCredential(WebAuthn)、OTPCredential、IdentityCredential(FedCM),以及已被取代的 FederatedCredential。

容器本身历史久、覆盖广:CredentialsContainer、get() 与 store() 见于 Chrome 51、Edge 18、Firefox 60 与 Safari 13;create() 见于 Chrome 60;preventSilentAccess() 见于 Chrome 60(Chrome 51 至 59 叫 requireUserMediation())、Edge 18、Firefox 60 与 Safari 17,而 Safari 13 至 16 暴露了该方法却对每次调用都以 NotSupportedError 拒绝(BCD api.CredentialsContainer)。凭据类型则窄得多:PasswordCredential 只存在于 Chrome 51,Firefox 与 Safari 任何版本都没有;get({ otp }) 见于 Chrome 93(Android 上 Chrome 84)与 Safari 27;get({ identity }) 见于 Chrome 108 与 Safari 27。本页讲容器;PublicKeyCredential 有独立条目。

navigator.credentials.get()
navigator.credentials.get(options)
navigator.credentials.store(credential)
navigator.credentials.create()
navigator.credentials.create(options)
navigator.credentials.preventSilentAccess()

get() 与 create() 返回 Promise<Credential?>:一个 Credential 子类实例,或者在 mediation 允许的用户介入程度内拿不到、建不出任何凭据时为 null。store() 与 preventSilentAccess() 返回 Promise<undefined>。四个方法都带 [SecureContext];在 localhost 以外的 http:// 源上容器为 undefined。

get() 接受 CredentialRequestOptions,create() 接受 CredentialCreationOptions,store() 接受一个 Credential,preventSilentAccess() 不接受参数。每种凭据类型往两个字典里各加一个成员;一次请求通过包含哪些成员来声明它接受哪些类型。

成员 类型 必填 说明
mediation(get、create) "silent"、"optional"、"conditional" 或 "required" 否 默认 "optional"。"silent" 不显示任何界面,直接以 null 兑现;"optional" 只在必要时显示选择器;"conditional" 把发现的凭据放进非模态界面(自动填充),并且不会很快以 null 兑现;"required" 忽略任何静默访问的许可,必定提示。对 create(),"conditional" 允许刚经过用户介入的登录免模态创建,否则拒绝。
uiMode(get) DOMString 否 "immediate":有凭据则显示模态选择器,否则立即以 null 完成且不提示。
signal(get、create) AbortSignal 否 中止时以该 signal 的 reason 拒绝(默认 AbortError,AbortSignal.timeout() 给出 TimeoutError),除非操作已经完成。
password(get) boolean 否 默认 false。true 表示索取已保存的 PasswordCredential。
password(create) PasswordCredentialData 或 HTMLFormElement 否 PasswordCredentialData 含 id(必填)、origin(必填)、password(必填)、name 与 iconURL;传表单时按其 autocomplete 标记读取。
federated(get、create) FederatedCredentialRequestOptions / FederatedCredentialInit 否 providers: sequence<USVString> 与 protocols: sequence<DOMString>,用于已被取代的 FederatedCredential。
publicKey(get、create) PublicKeyCredentialRequestOptions / PublicKeyCredentialCreationOptions 否 WebAuthn;见「WebAuthn 与通行密钥」条目。
otp(get) OTPCredentialRequestOptions 否 transport: ["sms"];兑现一个 OTPCredential,其 code 为短信一次性验证码。
identity(get) IdentityCredentialRequestOptions 否 providers 内含 configURL、clientId 及可选的 nonce、loginHint、domainHint;FedCM。

传给 store() 的 credential 必须是页面从 get() 得到的或自行构造的 Credential(new PasswordCredential(data) 或 new PasswordCredential(form))。

算法按固定顺序拒绝。凭据类型自身内部方法抛出的任何拒绝(例如 WebAuthn 超时的 NotAllowedError)原样透传。

异常 条件
InvalidStateError 文档不是 fully active(四个方法都适用)。Chromium 还在同一容器上另有请求进行中时使用它。
NotSupportedError get() 没有任何类型成员,或两个成员的类型不能共用一次请求;create() 带了多于一个类型成员,或没有关联文档。Safari 13 至 16 对每次 preventSilentAccess() 调用都以它拒绝,Chromium 在密码存储不可用时也以它拒绝。
SecurityError 从不透明源请求、创建或保存绑定源的类型(PasswordCredential、FederatedCredential)。
TypeError 对不支持的类型使用 mediation: "conditional" 或 uiMode: "immediate";create({ password }) 的数据或表单无法构造出 PasswordCredential。
NotAllowedError 请求的类型在当前 settings object 中已处于活动状态;该类型的 Permissions Policy 拒绝了文档;在与所有祖先不同源的上下文中收集或保存 PasswordCredential;没有事先同意就以 mediation: "conditional" 调用 create()。
signal 的中止原因 get() 或 create() 运行时 options.signal 已被中止,或在操作完成前被触发。

即使浏览器没有保存任何凭据,preventSilentAccess() 也会兑现;规范为它定义的唯一拒绝是 InvalidStateError。

示例除了检测 navigator.credentials,还检测 PasswordCredential,因为 Firefox 60+ 与 Safari 13+ 提供容器却没有这个类型:'credentials' in navigator 为真,而 get({ password: true }) 永远以 null 兑现(BCD api.PasswordCredential)。

进站时静默登录,带超时与可见的回退

Section titled “进站时静默登录,带超时与可见的回退”

mediation: "silent" 不显示选择器;需要用户同意时以 null 兑现,页面因此可以显示普通的登录按钮,而不是一个没人要的对话框。AbortSignal.timeout() 把卡住的存储变成可捕获的 TimeoutError。

async function trySilentSignIn() {
if (!('credentials' in navigator) || !('PasswordCredential' in window)) {
showSignInButton();
return;
}
try {
const cred = await navigator.credentials.get({
password: true,
mediation: 'silent',
signal: AbortSignal.timeout(5000),
});
if (!cred) return showSignInButton();
await postJson('/session', { id: cred.id, password: cred.password });
showSignedInState(cred.name ?? cred.id);
} catch (err) {
if (err.name === 'TimeoutError' || err.name === 'AbortError') return showSignInButton();
throw err;
}
}
window.addEventListener('load', trySilentSignIn);

TimeoutError 与 AbortError 都要分支处理:AbortSignal.timeout() 以前者拒绝,手动的 AbortController 以后者拒绝。

服务器接受凭据之后调用 store(),下次访问的静默 get() 才可能成功。用表单构造 PasswordCredential,让 autocomplete="username" 与 autocomplete="current-password" 字段提供 id 和 password。

document.querySelector('#sign-in').addEventListener('submit', async (event) => {
event.preventDefault();
const form = event.currentTarget;
const ok = await postForm('/session', new FormData(form));
if (!ok) return showError('用户名或密码错误');
if ('PasswordCredential' in window) {
try {
await navigator.credentials.store(new PasswordCredential(form));
} catch (err) {
console.warn(`store() failed: ${err.name}`); // 登录本身已经成功
}
}
location.assign('/home');
});

只有 Chrome 51+ 会走进这个分支;在 Firefox 与 Safari 中表单正常提交,由浏览器自带的密码管理器提议保存。

登出时清除静默访问,兼顾 Safari 13 至 16

Section titled “登出时清除静默访问,兼顾 Safari 13 至 16”

preventSilentAccess() 设置该源的标志,下一次 get() 就必须经过用户介入。在 Safari 13 至 16 上 typeof 检查能通过,但每次调用都以 NotSupportedError 拒绝,所以只捕获这一个名称并视为完成。

async function signOut() {
await fetch('/session', { method: 'DELETE' });
if (typeof navigator.credentials?.preventSilentAccess === 'function') {
try {
await navigator.credentials.preventSilentAccess();
} catch (err) {
if (err.name !== 'NotSupportedError') throw err;
}
}
location.assign('/');
}

只吞掉 NotSupportedError;文档已分离导致的 InvalidStateError 仍会抛出。

规范

规范状态
Credential Management Level 1: get() methodW3C
Credential Management Level 1: preventSilentAccess() methodW3C