iOS WebAuthn问题:关闭弹窗后无法重唤且提示用户取消请求错误
iOS WebAuthn弹窗取消后无法重新唤起的解决方案
在iOS的Safari或内嵌WKWebView中使用WebAuthn时,用户关闭弹窗后再次触发请求会抛出This request has been canceled by the user错误,这是iOS WebKit实现的特性——WebAuthn请求的上下文在用户取消后会被标记为失效,无法复用原有参数重新唤起。以下是具体的解决方法:
1. 每次唤起都获取全新的WebAuthn请求参数
iOS WebAuthn会绑定请求的唯一挑战(challenge)和上下文状态,用户取消后,原请求的参数(尤其是publicKey对象)会被视为无效。必须每次触发WebAuthn流程时,都从后端重新获取最新的注册/登录选项,不能缓存或复用之前的参数。
示例代码:
async function triggerWebAuthnLogin() { try { // 每次都从后端请求新鲜的options,而不是复用缓存值 const freshOptions = await fetch('/api/webauthn/get-login-options') .then(res => res.json()); const credential = await navigator.credentials.get({ publicKey: freshOptions }); // 将凭证提交给后端完成验证 await fetch('/api/webauthn/verify-login', { method: 'POST', body: JSON.stringify(credential) }); } catch (err) { if (err.name === 'NotAllowedError' && err.message.includes('canceled by the user')) { // 用户主动取消,无需额外处理,下次点击会重新发起请求 console.log('用户取消了WebAuthn验证'); } else { // 处理其他错误 console.error('WebAuthn请求失败:', err); } } }
2. 防止重复触发请求
如果用户快速多次点击触发按钮,可能导致多个WebAuthn请求叠加,iOS会直接拒绝后续请求。需要添加状态锁,在请求进行中禁用触发按钮,避免重复调用。
示例代码:
let isWebAuthnInProgress = false; async function triggerWebAuthnLogin() { if (isWebAuthnInProgress) return; isWebAuthnInProgress = true; try { const freshOptions = await fetch('/api/webauthn/get-login-options') .then(res => res.json()); const credential = await navigator.credentials.get({ publicKey: freshOptions }); await fetch('/api/webauthn/verify-login', { method: 'POST', body: JSON.stringify(credential) }); } catch (err) { // 错误处理逻辑 } finally { isWebAuthnInProgress = false; } }
3. 内嵌WKWebView场景的额外配置
如果是在iOS App的WKWebView中使用WebAuthn,还需要确保以下配置正确:
- 确保App运行在iOS 13.0及以上版本(WebAuthn从iOS 13开始支持)
- WKWebConfiguration需开启JavaScript支持(默认开启,但需确认未禁用)
- 如果使用生物识别(FaceID/TouchID),需在Info.plist中添加对应的权限描述(如
NSFaceIDUsageDescription) - 同样遵循“每次请求获取新参数”的规则,不能复用WebView中的旧请求上下文
为什么会出现这个问题?
iOS WebKit为了安全和请求生命周期管理,会在用户取消WebAuthn弹窗后销毁当前请求的上下文。后端生成的WebAuthn选项包含一次性的挑战值,这也是安全设计的一部分——每个请求的挑战都是唯一的,无法重复使用,因此必须重新获取新的选项才能发起有效的WebAuthn请求。
内容的提问来源于stack exchange,提问作者Xaber
相关产品推荐
相关产品推荐

