如何使用 WebAuthn、Next.js 和 NestJS 构建 Passkey 认证

如何使用 WebAuthn、Next.js 和 NestJS 构建 Passkey 认证

💡 原文英文,约6300词,阅读约需23分钟。
📝

内容提要

本文介绍如何使用 NestJS、Next.js 和 SimpleWebAuthn 构建无密码认证应用。Passkey 基于公钥加密,私钥存于设备并由指纹、面容或锁屏保护,服务器仅保存公钥,可抵御钓鱼、密码复用和数据泄露。教程涵盖注册与登录的挑战生成、响应验证、JWT 保护路由,并提醒避免凭证 ID 重复编码、未校验凭证归属等安全错误。

🔎

延伸解读

Passkey 的安全边界:设备与浏览器是信任锚点

文章指出,Passkey 将安全责任从用户转移到浏览器和操作系统:RP ID 绑定域名,使钓鱼网站无法使用已注册的凭证;私钥永不离开设备,服务器只存公钥。但这也意味着安全性依赖设备本身——若设备被入侵或用户未设置锁屏,保护会减弱。此外,应用无法强制使用指纹或面容,只能通过 UV 标志要求设备完成本地验证,具体方式由操作系统决定。

开发实现中的关键校验点

教程强调服务端必须校验挑战、来源和 RP ID,并设置 requireUserVerification: true 以确保用户验证发生。注册时需检查邮箱是否已有凭证,防止攻击者将自有 Passkey 绑定到他人账户;登录时查询凭证必须同时限定用户 ID,避免跨账户冒用。此外,挑战用后应立即清空,防止重放。这些细节是 Passkey 安全落地的核心,而非可选步骤。

同步 Passkey 与计数器机制的取舍

文章提到,部分认证器会递增签名计数器以检测克隆设备,但同步 Passkey(如 iCloud 钥匙串)通常将计数器保持为零。SimpleWebAuthn 同时支持这两种场景。开发者需理解:计数器并非万能防克隆手段,对于同步凭证,其安全更多依赖平台同步机制。数据库中的 deviceType 和 backedUp 字段可帮助识别凭证是否可同步及备份状态,便于后续策略调整。

从教程到生产:需替换的临时配置

示例中为方便本地开发,CORS 设置为 origin: true 接受任意来源,JWT 存储在 localStorage,且 TypeORM 使用 synchronize 自动建表。文章明确提醒,生产环境应将 CORS 限定为前端域名、改用 httpOnly cookie 存储令牌,并用迁移替代 synchronize。这些调整虽小,却直接影响应用的安全性和可维护性,读者在部署前务必逐项检查。

❓

Q&A

Passkey 认证相比传统密码有哪些优势?

Passkey 基于公钥加密,私钥仅存于用户设备并由生物识别或锁屏保护,服务器只保存公钥。因此能抵御钓鱼攻击(浏览器只在创建 passkey 的域名下使用)、避免密码复用(每个网站独立密钥对)、即使数据库泄露攻击者也无法登录,且无需用户记忆或管理密码。

使用 WebAuthn 实现 passkey 注册和登录的基本流程是什么?

注册和登录都遵循三步:1)服务器生成随机挑战(challenge)并发送给浏览器;2)浏览器要求设备验证用户(指纹/面容/锁屏)并对挑战签名;3)浏览器将签名响应发回服务器验证。注册时设备生成新密钥对并返回公钥,服务器保存公钥;登录时设备用已有私钥签名新挑战,服务器用保存的公钥验证签名。

在 NestJS 后端中,如何配置 WebAuthn 的 RP ID 和 Origin?

在 .env 文件中设置 WEBAUTHN_RP_ID(仅域名,如 localhost,不含协议和端口)和 WEBAUTHN_ORIGIN(前端完整 URL,如 http://localhost:3000)。服务器会拒绝来自不同 origin 的响应。开发时使用 localhost 即可,因为浏览器允许在 HTTPS 或 localhost 上使用 WebAuthn。

构建 passkey 认证时有哪些常见的安全错误需要避免?

文章列出五个安全错误:1)对凭证 ID 进行双重编码;2)用 undefined 清除挑战;3)查找凭证时不校验归属(必须同时用 credentialId 和 userId 查询);4)允许注册接口为已有账户添加 passkey(否则任何人知道邮箱即可绑定自己的 passkey);5)请求用户验证但不强制要求(应设置 requireUserVerification: true)。

Passkey 注册时,服务器如何确保用户确实进行了生物识别验证?

服务器在生成注册选项时设置 authenticatorSelection.userVerification 为 'required',并在验证响应时设置 requireUserVerification: true。这样设备只有在用户完成指纹、面容或锁屏检查后才会设置 UV 标志,服务器会检查该标志,若未设置则拒绝响应。

在 Next.js 前端中,如何使用 SimpleWebAuthn 的浏览器库完成注册和登录?

前端使用 @simplewebauthn/browser 的 startRegistration 和 startAuthentication 函数。注册时:从服务器获取选项,调用 startRegistration({ optionsJSON: options }),将结果发回服务器验证。登录时类似:获取认证选项,调用 startAuthentication({ optionsJSON: options }),验证成功后服务器返回 JWT,前端存储 token 并跳转。

🏷️

标签

➡️

继续阅读