Auth.js — 把 OAuth 登录和会话存储拆成两层抽象
已复核Auth.js 是一套把“用哪家 IdP 登录”和“会话存哪里”拆开的认证库。日常类比:多用插座——一头插 GitHub / Google / 邮件,另一头插 JWT cookie 或数据库 session;中间只吃 Web 标准 Request / Response。
你写:
import NextAuth from "next-auth"import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [GitHub],})框架包 next-auth 只做 Next.js 适配,返回 handlers / auth / signIn / signOut。真正的状态机在 @auth/core:Auth(request, config) 解析 action,再分发到 signin、callback、session、signout。
本文绑定 next-auth@5.0.0-beta.32(workspace 内 @auth/core@0.41.3)。npm next-auth@latest 仍是 v4 4.24.15,不能把网上 v4 教程当成这个 revision。
不理解 Auth.js,下面这些事会对不上:
- 为什么同一套
providers: [GitHub]能接 Next.js App Router,也能被其他框架薄封装调用 - 为什么加 adapter 后默认改走 database session,显式写
session.strategy: "jwt"才能继续无状态 - 为什么
callbacks.jwt和callbacks.session要成对写——cookie 里的是加密 JWE,前端默认只看到 name/email/image - 为什么 Credentials 不能当“邮箱密码全家桶”——固定源码故意限制它,且只允许 JWT session
固定 5.0.0-beta.32 可以拆成四层:
-
核心吃 Web 标准:
Auth先toInternalRequest/assertConfig,再进入AuthInternal。GET 渲染内置页或读 session;POST 的signin/signout/session要过 CSRF。 -
Provider 是配置工厂:每个 provider 返回带
id/type/ 端点字段的对象,不是 class。OAuth/OIDC 走oauth4webapi;Credentials 的authorize默认返回null,输入也不做校验。 -
Session 策略由 adapter 决定默认值:无 adapter →
jwt;有 adapter →database。maxAge默认 30 天,updateAge默认 1 天。JWT 实际是dir+A256CBC-HS512的 JWE,用secret/AUTH_SECRET派生密钥。 -
Adapter 方法全部可选:
createUser/createSession/createVerificationToken等都带?。纯 JWT 不必实现 session 表;Credentials 用户也不会写入 adapter。
案例 1:Next.js 最小 GitHub 登录
Section titled “案例 1:Next.js 最小 GitHub 登录”import NextAuth from "next-auth"import GitHub from "next-auth/providers/github"
export const { handlers, auth } = NextAuth({ providers: [GitHub],})
// app/api/auth/[...nextauth]/route.tsexport { handlers as GET, handlers as POST } from "../../../../auth"handlers.GET/POST 直接把 NextRequest 交给 Auth。环境变量按 AUTH_GITHUB_ID / AUTH_GITHUB_SECRET / AUTH_SECRET 推断;生产缺 AUTH_SECRET 会在配置断言阶段失败。
案例 2:有无 adapter 会改变默认 session
Section titled “案例 2:有无 adapter 会改变默认 session”NextAuth({ adapter: PrismaAdapter(prisma), providers: [GitHub], // 有 adapter 时默认 strategy: "database" session: { strategy: "jwt" }, // 想继续无状态必须显式写})database 模式下 cookie 只留 sessionToken,每次查库。JWT 模式下 cookie 是加密 token;删 cookie 不能作废用户手里的副本。
案例 3:自定义字段必须经过两道 callback
Section titled “案例 3:自定义字段必须经过两道 callback”callbacks: { jwt({ token, user }) { if (user) token.role = user.role return token }, session({ session, token }) { session.user.role = token.role return session },}默认 session callback 只返回 name/email/image。服务端 auth() 会再包一层,把 user 与 session 合并,但客户端 /session 仍只看到你显式放回去的字段。
- 把 npm latest 当成 Auth.js v5:
latest=4.24.15,v5 在next-auth@beta。getServerSession是 v4 API。 - 以为 JWT 只是签名:固定实现默认加密 JWE;
jwt模块源码还标了“将会重构,不要当稳定公共合同”。 - Credentials 当生产密码登录:源码写明故意限制,且用户不落库;hash、限流、防爆破都要自己做。
- Edge 上带着 Node-only adapter:adapter 可能依赖 Node API。核心本身吃 Web 标准,不保证 Prisma 一类客户端能进 middleware。
- POST session 失败却去找 redirect:CSRF 失败时
Auth对 POST/session直接400JSON,不跳错误页。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 需要自托管、多 IdP,并且接受 Web 标准 Request/Response 的 JS 运行时
- Next.js 14/15/16 + React 18/19(本包 peer 范围)
- 想用官方 adapter 做 database session,或显式 JWT 做无状态
不适用:
- 要稳定 semver 的生产依赖——本页绑定的是 beta,
@auth/core入口也标了 Experimental - 要开箱 UI / 组织 / MFA 全家桶——这不是本库合同
- 想抄 200 行自己管 cookie——看 lucia 的单文件样本,不要装这个框架
- 只能用 v4 文档和
NEXTAUTH_*旧心智,又不想迁 env / API
固定版本边界
Section titled “固定版本边界”- 本文绑定
nextauthjs/next-auth@e293b374...,tagnext-auth@5.0.0-beta.32,其中@auth/core版本文件为0.41.3。 @auth/core@0.41.3单独 tag 是祖先提交5af7357f...;两者源码差一个 package metadata chore,已记录在共享审查文档。- 未安装依赖、未跑上游测试、未走真实 OAuth。状态保持
UNVERIFIED。
- 框架无关核心靠 IO 标准,不靠“少写代码”——适配层只转换协议,状态机仍在
@auth/core。 - 默认值会随 adapter 翻转——同一份配置加一行 adapter,session 就从 JWE 变成查库。
- 暴露给客户端的 session 是过滤后的视图——token 里有的东西,默认不会自动出现在
useSession。 - 限制 Credentials 是产品决策——不是文档疏忽;密码风险被故意留给调用方。
- 不写
session.strategy、也不给 adapter,固定版本默认用哪种 session? - 给了 adapter 却想继续无状态 cookie,要改哪一个字段?
- Credentials 登录成功的用户,会被写入 adapter 的 User 表吗?
检查点:
jwt。无 adapter 时init强制 JSON Web Token(实际是 JWE)。- 显式
session: { strategy: "jwt" }。有 adapter 时默认改成database。 - 不会。源码写明 Credentials 用户不持久化到数据库,也因此只能配 JWT session。
- 文档:authjs.dev(v5 主站;安装示例写
next-auth@beta) - 固定源码:nextauthjs/next-auth —— 本文绑定
e293b3746616660f0844347a68d09eac54b95c6f - lucia —— 对照:弃用后的单文件 session 样本,不再提供 framework
- better-auth —— 插件式自托管对照,不在本页绑定范围
- prisma —— 常见 adapter 实现之一,Edge 兼容性要单独核对该客户端
- next-js ——
next-auth的宿主;handlers 走 App Router Route Handler - lucia —— utility / 抄代码对照
- better-auth —— 同期自托管认证框架
- prisma —— 官方 adapter 生态中最常见的 ORM
- zod —— Credentials 文档建议用来补输入校验,库本身不做
- appwrite —— Appwrite — 自己能装一遍的开源 Firebase
- better-auth —— better-auth — 把登录/OAuth/2FA/Passkey 拼成一行配置的 TS 认证框架
- clerk —— Clerk — 把登录注册组织 MFA 整套外包给云的 SaaS 认证 SDK
- lucia —— Lucia — 主动把自己降级为”学习资源”的 TS 认证库
- supabase —— Supabase — Firebase 的开源替代
- supertokens —— SuperTokens — 自托管认证框架,把登录方式做成可拼装的 Recipe