如何用 next-auth Passkeys(WebAuthn)5分钟开启无密码登录
【免费下载链接】next-authAuthentication for the Web.项目地址: https://gitcode.com/gh_mirrors/ne/next-auth
"忘记密码"工单没完没了,密码库还可能被拖库。如果你的项目用 next-auth(Auth.js)做认证,现在可以直接开启 Passkey 无密码登录:用户靠指纹或 Face ID 验证,服务端只存公钥,不碰任何密码。
适用场景与你将获得
这一节先确认你是否适合上 Passkeys,以及做完能拿到什么效果。
谁适合用:
- 有邮箱注册/登录流程,想加一种无密码方式的团队
- 正在用
next-authv5(beta)+ Prisma 适配器的 Next.js 项目 - 有公网 HTTPS 部署环境(WebAuthn 强制安全上下文,
localhost除外)
最终效果:
- 内置
/signin页面自动出现 "Sign in with Passkeys" 按钮 - 已登录用户可再注册多枚 Passkey(换手机、换浏览器都方便)
- 自定义登录页可通过
signIn("passkey")两行代码完成登录/注册
环境与版本要求(缺一不可):
| 依赖 | 最低版本 |
|---|---|
next-auth | 5.0.0-beta.8 |
@auth/prisma-adapter | 1.3.0 |
| Node.js | 20.0.0 |
@simplewebauthn/server | 9.0.3 |
@simplewebauthn/browser | 9.0.1(仅自定义登录页需要) |
官方文档明确标注该 provider 仍属实验功能(experimental),生产环境建议与既有登录方式并行。
快速上手:5分钟最小化路径
本节按"看到什么→做什么"走一遍最短路径,全程 4 步,最后你在/signin看到 Passkey 按钮并成功登录。
第一步:安装 WebAuthn 核心依赖
SimpleWebAuthn 是 Auth.js 底层用的协议处理库,server 端做挑战生成与签名校验,browser 端驱动设备交互:
npm install @simplewebauthn/server@9.0.3 @simplewebauthn/browser@9.0.1第二步:给数据库加 Authenticator 表
Passkey 需要一张表存设备凭证(公钥、计数器、设备类型)。以 Prisma 为例,在schema.prisma追加:
// Optional for WebAuthn support —— WebAuthn 专用表 model Authenticator { credentialID String @unique // 凭证 ID,全局唯一 userId String providerAccountId String credentialPublicKey String // 只存公钥,私钥留在设备 counter Int // 防重放的签名计数器 credentialDeviceType String credentialBackedUp Boolean transports String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@id([userId, credentialID]) }执行npx prisma migrate dev生成迁移。其他数据库的建表脚本可参考 Prisma 适配器文档。
第三步:配置 Passkey provider
修改auth.ts,注册 provider 并打开实验开关:
import Passkey from "next-auth/providers/passkey" import { PrismaAdapter } from "@auth/prisma-adapter" import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() export default { adapter: PrismaAdapter(prisma), providers: [Passkey], experimental: { enableWebAuthn: true }, // 关键:启用 WebAuthn 实验特性 }第四步:访问 /signin,完成第一次登录
重启应用后打开/signin,页面上会多出一组 Passkey 表单项(默认含 email 字段)和一个 Sign in with Passkeys 按钮:
输入邮箱提交:
- 邮箱不存在→ 自动走注册流程,设备创建 Passkey,账号同时建立
- 邮箱已存在→ 直接走认证流程,指纹一按即登录
登录成功后,/api/auth/session返回的 session 结构和其他 provider 一致:
附:自定义登录页写法
如果不用内置页面,next-auth/webauthn导出的signIn可以接管整个流程,前提是装了@simplewebauthn/browser:
"use client" import { useSession } from "next-auth/react" import { signIn } from "next-auth/webauthn" // 注意不是 next-auth/react export default function Login() { const { status } = useSession() return ( <> {status === "authenticated" ? ( // 已登录:注册新的 Passkey <button onClick={() => signIn("passkey", { action: "register" })}> Register new Passkey </button> ) : ( <button onClick={() => signIn("passkey")}>Sign in with Passkey</button> )} </> ) }幕后原理:它是怎么工作的
看完动手,再回头看机制。整个流程分 4 步,核心代码在 packages/core/src/lib/utils/webauthn-utils.ts。
- 拉取挑战(options):页面先 GET
/api/auth/webauthn-options/passkey。服务端用inferWebAuthnOptions判断该次请求是注册还是认证(依据:是否登录、表单里邮箱是否已存在),然后经@simplewebauthn/server生成一个随机 challenge,把选项下发给浏览器,challenge 本身签进 cookie 暂存。 - 设备交互(attestation):浏览器调 WebAuthn API(
startRegistration/startAuthentication),用户用 Touch ID、Windows Hello 或设备 PIN 确认。设备生成密钥对,私钥永不出设备,返回凭据 ID、公钥和一段签名。 - 服务端验证(verification):客户端把响应 POST 到
/callback/passkey。服务端从 cookie 取出 challenge,调verifyAuthenticationResponse/verifyRegistrationResponse校验签名、origin和rpID是否匹配。 - 落库并建立会话:注册时把公钥、
counter、设备类型写入Authenticator表并创建账号;认证时校验通过后用updateAuthenticatorCounter递增计数器,再走正常signIn逻辑发 session。
与密码方案的关键差别:
| 对比项 | 传统密码 | Passkeys(WebAuthn) |
|---|---|---|
| 服务端存储 | 哈希后的密码,拖库仍会撞库 | 只有公钥,拖库也换不出密码 |
| 钓鱼防御 | 假站点骗到就输密码 | challenge 绑定域名,假站点拿不到合法签名 |
| 用户侧 | 记密码、改密码、找回流程 | 零记忆负担,生物识别解锁 |
| 重放攻击 | 依赖限流、验证码 | counter单调递增,重放签名直接拒绝 |
一句话:密码是"你知道什么",Passkey 是"你手上有什么",而"什么"始终在设备里。
实战踩坑与常见问题
这些现象在调试阶段出现频率最高,按"现象→原因→解决"处理。
1./signin页面没有 Passkey 按钮
- 现象:配置了
providers: [Passkey]但页面没变化 - 原因:漏了
experimental: { enableWebAuthn: true },或next-auth版本低于5.0.0-beta.8 - 解决:补上开关并升级
next-auth,重启后按钮出现
2. 自定义页面报Provider id "passkey" does not refer to a WebAuthn provider
- 现象:点按钮直接 TypeError,控制台无后续流程
- 原因:误用了
next-auth/react的signIn,WebAuthn 流程必须由next-auth/webauthn发起 - 解决:改成
import { signIn } from "next-auth/webauthn"
3. 报WebAuthn authenticator not found in database或WebAuthnVerificationError
- 现象:设备弹窗过了,服务端却 401/500
- 原因:
Authenticator表没建迁移,或适配器低于1.3.0没实现getAuthenticator/updateAuthenticatorCounter - 解决:执行第二步的迁移,升级
@auth/prisma-adapter到1.3.0+
4. 注册按钮点了没反应 / 报 MissingWebAuthnAutocomplete
- 现象:开了
enableConditionalUI后控制台报错 - 原因:条件式 UI 要求表单字段的
autocomplete属性包含webauthn - 解决:在 provider 的
formFields中给字段加autocomplete: "webauthn";暂时不用可先关掉enableConditionalUI
5. 真机测试永远失败,localhost 却正常
- 现象:浏览器不弹任何设备认证框
- 原因:WebAuthn 只在安全上下文(HTTPS 或
localhost)下可用,测试机用了 HTTP - 解决:用 HTTPS 域名部署,或用
ngrok/云厂商免费证书临时验证
上线前检查清单
区分必须项和建议项,必须项任一不满足就不要放量。
必须项:
next-auth >= 5.0.0-beta.8、@auth/prisma-adapter >= 1.3.0、Node >= 20experimental: { enableWebAuthn: true }已开启Authenticator表迁移已在生产库执行- 生产环境为 HTTPS(WebAuthn 硬性要求)
- 已知悉官方结论:该 provider 标记为 experimental,不建议直接作为唯一登录方式
建议项:
- 保留邮箱魔法链接或 OAuth 作为备用登录方式,方便设备丢失/换机
- 显式配置
relayingParty(rp.id/rp.name/origin),避免多域名部署时 origin 推断出错 - 关注
counter更新失败的告警日志(AdapterError),它会导致后续认证全部失败 - 灰度开放:先对小比例用户展示 Passkey 按钮,观察注册/认证成功率
- 用真实设备(手机 + 电脑各一)走一遍"注册→换设备登录→再注册第二枚"完整链路
结语
Passkey 登录把"记住密码"从你的产品里彻底删掉,剩下的工作就是维护好挑战、公钥和计数器这三样。完整 API 参考见 WebAuthn 官方文档,各框架接入方式可翻 示例项目,provider 默认配置在 passkey.ts。
【免费下载链接】next-authAuthentication for the Web.项目地址: https://gitcode.com/gh_mirrors/ne/next-auth
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考