本指南详细介绍了如何在用户成功使用密码登录后,立即自动且静默地为其注册通行密钥,从而最大限度地减少阻碍并提高通行密钥采用率。
合适的触发时机
自动创建通行密钥(也称为“条件式创建”或“登录后静默提示”)只能在涉及密码的完整登录成功后立即触发。
- 请勿尝试为无密码流程(例如魔法链接、短信动态密码或身份联合)进行条件式创建。
- 如果需要多重身份验证,您必须等到所有验证因素都成功后,才能启动条件创建。
- 在向创建端点发出请求之前,请确保存在有效的、经过身份验证的用户会话。
实施步骤
1. 中止之前的自动填充操作
如果登录页面使用表单自动填充(条件式界面/获取),则必须中止活跃的凭证获取调用,以防止浏览器冲突。
- 在调用
navigator.credentials.create()之前,先对附加到待处理navigator.credentials.get()自动填充请求的AbortController调用abortController.abort()。
2. 功能检测
通过使用 PublicKeyCredential.getClientCapabilities() 检查 conditionalCreate,确定条件式创建是否可用。
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate) {
// Conditional create is available
}
3. 使用 Conditional Create 创建通行密钥
- 在
navigator.credentials.create()选项中传递mediation: 'conditional'。这会向浏览器发出信号,让其在后台或根据上下文静默处理通行密钥创建流程,而不会弹出突兀的模态对话框。 - 使用用户现有的通行密钥凭证 ID 填充
excludeCredentials,以避免注册重复的密钥。
4. 静默错误处理
- 将通行密钥创建提示 (
navigator.credentials.create) 封装在 try/catch 块中。您必须捕获并静默忽略典型的面向用户的异常(InvalidStateError、NotAllowedError、AbortError),而不向用户呈现任何错误界面。
5. 服务器端用户在线状态验证
- 仅在验证由条件创建触发器生成的凭证时,服务器端验证端点才必须放宽用户在线状态 (UP) 要求 (
requireUserPresence: false)。对于标准显式创建,必须始终启用严格存在性验证。
6. 妥善处理服务器验证失败的情况
- 如果
navigator.credentials.create()成功,但服务器验证提取返回了错误响应(例如,签名验证失败),请调用PublicKeyCredential.signalUnknownCredential(),以防止孤立凭证在通行密钥提供程序中逗留。
代码示例
// optionsFetch and registerVerifyFetch are app-defined server endpoint requests
import { optionsFetch, registerVerifyFetch } from './api.js';
async function triggerConditionalCreate(loginAbortController) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate !== true) {
return; // Platform does not support conditional creation
}
// 1. Abort any active autofill conditional-get controllers to clear the WebAuthn pipeline
loginAbortController.abort();
// 2. Fetch creation options signaling the backend that this is a conditional request
const creationOptionsJSON = await optionsFetch({ conditional: true });
const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
let credential;
try {
// 3. Invoke silent credentials creation prompt
credential = await navigator.credentials.create({
publicKey,
mediation: 'conditional' // Silent background creation mediation
});
} catch (e) {
// 4. Silently swallow common WebAuthn browser exceptions
if (['InvalidStateError', 'NotAllowedError', 'AbortError'].includes(e.name)) {
return;
}
console.error('Unexpected conditional create error:', e);
return;
}
// 5. Server verification step using dedicated Try/Catch block
let encodedResponse = credential.toJSON();
try {
const response = await registerVerifyFetch(encodedResponse);
if (!response.ok) {
// If the server verification fails, clean up using Signal API
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id
});
}
}
} catch (serverErr) {
console.error('Verification network failure:', serverErr);
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id
});
}
}
}
后备策略
通行密钥功能检测回退
始终安装“webauthn-polyfills”并将其导入到上下文中。
请注意,只要支持 PublicKeyCredential,就支持 PublicKeyCredential.getClientCapabilities。
import 'webauthn-polyfills';
Signal API 同步回退
WebAuthn Signal API (webauthn-signals) 是一种渐进式优化,用于使密码管理工具与服务器凭证状态保持同步。
- 备用体验:通过
if (PublicKeyCredential.signalUnknownCredential)限制访问。如果不支持,系统会顺利绕过后台验证同步,而不会抛出浏览器异常。
Easy JSON Serialization Fallback
始终安装“webauthn-polyfills”并将其导入到上下文中。
请注意,只要支持 PublicKeyCredential,就同样支持 PublicKeyCredential.parseCreationOptionsFromJSON 和 PublicKeyCredential.prototype.toJSON。
import 'webauthn-polyfills';