难点:userActivation 用户瞬态激活
navigator.userActivation.isActive
是的,你的理解很准确。由于 window.open 会消耗掉用户激活状态,你无法在用它打开的弹出窗口中,再“自动”调用 Passkey(WebAuthn) API 发起验证。这意味着,弹窗内的 Passkey 调用也必须由用户在该窗口内直接、明确的点击等操作来触发。
你可以把它理解成一次“接力”:父页面的点击消耗了激活状态去打开弹窗,而弹窗内的 WebAuthn 操作,需要用户在弹窗内进行第二次交互来重新获取一个新的激活状态。
💡 替代方案与实践思路
虽然被动弹窗自动调用这条路走不通,但业界已经有了成熟的方案来实现流畅的 Passkey 体验:
- 优先使用“条件式 UI”
这是一种让用户与页面主表单交互(如点击输入框),然后浏览器自动弹出密钥提示的被动方式,完全不需要
window.open。建议优先在 Relying Party ID 同源的主窗口页面中采用。 - 在弹出窗口内主动调用 WebAuthn API
如果业务上确实需要弹窗,你的流程必须调整为:用户点击 父页面 按钮 → 代码调用
window.open()打开弹窗 → 用户在 弹窗内 点击“验证”按钮 → 此时才调用navigator.credentials.get()发起验证。这个模式依赖于用户在新窗口中的主动交互。// 在弹出窗口 (popup.html) 内部的 JavaScript 代码const authButton = document.getElementById('authenticate-with-passkey');authButton.addEventListener('click', async () => {// 这里的 'click' 事件重新为用户提供了激活状态try {const assertion = await navigator.credentials.get({publicKey: {challenge: /* 从服务器获取的挑战码 */,rpId: "your-site.com",// ...其他必要的 WebAuthn 参数}});// 验证通过后,将 assertion 发送回服务器} catch (err) {console.error("Passkey authentication failed", err);}}); - 关于浏览器兼容性的额外说明
需要注意的是,不同浏览器对激活状态的消耗策略存在细微差别,这个流程也受到影响:
- Chromium 浏览器 (Chrome, Edge 等):在父页面调用
window.open时会消耗激活状态,因此必须按照上述“先弹窗、后触发”的模式来调用 WebAuthn API。 - WebKit 浏览器 (Safari):情况比较特殊。它既不消耗激活状态,也可能允许在跨 iframe 中更宽松地调用 WebAuthn。
- 建议:为确保各浏览器体验一致,最好统一采用依赖用户主动点击的方式触发。
- Chromium 浏览器 (Chrome, Edge 等):在父页面调用
在具体实现中,你是打算通过 iframe 还是弹窗来嵌入这部分功能呢?如果有具体的业务场景(比如在 Chrome 或 Safari 上遇到拦截),欢迎告诉我,我帮你看看有没有更合适的落地方案。