☰
如何用 next-auth Passkeys(WebAuthn)5分钟开启无密码登录
2026/10/29 1:50:40 网站建设 项目流程

如何用 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-auth5.0.0-beta.8
@auth/prisma-adapter1.3.0
Node.js20.0.0
@simplewebauthn/server9.0.3
@simplewebauthn/browser9.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。

  1. 拉取挑战(options):页面先 GET/api/auth/webauthn-options/passkey。服务端用inferWebAuthnOptions判断该次请求是注册还是认证(依据:是否登录、表单里邮箱是否已存在),然后经@simplewebauthn/server生成一个随机 challenge,把选项下发给浏览器,challenge 本身签进 cookie 暂存。
  2. 设备交互(attestation):浏览器调 WebAuthn API(startRegistration/startAuthentication),用户用 Touch ID、Windows Hello 或设备 PIN 确认。设备生成密钥对,私钥永不出设备,返回凭据 ID、公钥和一段签名。
  3. 服务端验证(verification):客户端把响应 POST 到/callback/passkey。服务端从 cookie 取出 challenge,调verifyAuthenticationResponse/verifyRegistrationResponse校验签名、origin和rpID是否匹配。
  4. 落库并建立会话:注册时把公钥、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 >= 20
  • experimental: { 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询