Skip to main content

难点: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。
    • 建议:为确保各浏览器体验一致,最好统一采用依赖用户主动点击的方式触发。

在具体实现中,你是打算通过 iframe 还是弹窗来嵌入这部分功能呢?如果有具体的业务场景(比如在 Chrome 或 Safari 上遇到拦截),欢迎告诉我,我帮你看看有没有更合适的落地方案。