WHAT - NextAuth 登录流程架构 文章目录1. Provider认证入口2. authorize()3. jwt() Callback常见写法4. session() Callback为什么要两层5. auth()6. MiddlewareRBAC 权限控制7. 登录全过程源码视角Step1Step2Step3Step4Step5Step6Step7Step8Step98. 真正推荐的企业级写法9. 具体例子场景第一步authorize()第二步jwt()第三步session()为什么还要 Session一个真实企业场景生命周期最终可以这样理解在 WHAT - NextAuth 权限认证机制 中我们介绍了 NextAuth 是什么以及优缺点。下一步最重要的是理解它内部的数据流。可以把 NextAuth 看成一个认证状态机┌─────────────┐ │ Provider │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ authorize │ └──────┬──────┘ │ User ▼ ┌─────────────┐ │ callbacks │ │ jwt() │ └──────┬──────┘ │ Token ▼ ┌─────────────┐ │ callbacks │ │ session() │ └──────┬──────┘ │ Session ▼ ┌─────────────┐ │ auth() │ │ useSession()│ └──────┬──────┘ │ ▼ ┌─────────────┐ │ Middleware │ └─────────────┘1. Provider认证入口Provider 决定用户如何登录。例如 GitHubproviders:[GitHub({clientId,clientSecret})]Credentialsproviders:[Credentials({asyncauthorize(credentials){...}})]本质上Provider 获取用户身份信息不同 Provider 获取方式不同Provider获取用户方式GitHubOAuthGoogleOAuthCredentials用户名密码EmailMagic Link2. authorize()只有 Credentials Provider 有这个步骤。例如Credentials({asyncauthorize(credentials){constuserawaitdb.user.findUnique({email:credentials.email})if(!user){returnnull}returnuser}})这里返回的 user 非常重要。return{id:1,name:Tom,role:admin}这个对象会流入后续authorize() ↓ jwt() ↓ session()3. jwt() Callback最核心的 Callback。callbacks:{asyncjwt({token,user}){returntoken}}第一次登录{token:{},user:{id:1,role:admin}}后续请求{token:{id:1,role:admin},user:undefined}因为用户信息已经存进 JWT。常见写法把数据库字段塞进 JWTcallbacks:{asyncjwt({token,user}){if(user){token.iduser.id token.roleuser.role}returntoken}}结果{sub:1,id:1,role:admin}JWT 会被加密后放到 Cookie。Cookie ↓ JWT ↓ roleadmin4. session() Callback客户端拿不到 JWT。客户端拿的是 Session。callbacks:{asyncsession({session,token}){session.user.idtoken.id session.user.roletoken.rolereturnsession}}流程JWT ↓ session() ↓ Session例如JWT{id:1,role:admin}Session{user:{id:1,role:admin}}为什么要两层因为JWT 服务端真实身份 Session 暴露给前端的数据你可以过滤敏感信息session.user.roletoken.role// 不暴露token.accessToken token.refreshToken5. auth()App Router 新版本最重要的 API。constsessionawaitauth()内部其实在做Cookie ↓ 读取 JWT ↓ 解密 JWT ↓ 执行 session() ↓ 返回 Session所以constsessionawaitauth()相当于constusergetCurrentUser()6. Middleware用于页面保护。export{authasmiddleware}from/auth或者exportdefaultauth((req){if(!req.auth){returnResponse.redirect(/login)}})执行流程请求页面 ↓ Middleware ↓ 读取 Cookie ↓ 解析 JWT ↓ req.auth ↓ 允许/拒绝RBAC 权限控制例如exportdefaultauth((req){if(req.auth?.user.role!admin){returnResponse.redirect(/)}})这样admin ✓ user ✗7. 登录全过程源码视角以 GitHub 登录为例Step1点击登录signIn(github)浏览器跳转/auth/signin/githubStep2重定向 GitHubGitHub OAuth用户授权AllowStep3GitHub 返回 codecallback?codexxxStep4NextAuth 交换 Tokencode ↓ access_token调用 GitHub APIGET /user获得{id:123,login:tom}Step5生成 User内部统一转换user{id:123,name:Tom}Step6执行 jwt()jwt({token,user})得到{id:123,role:admin}Step7生成 Sessionsession({session,token})得到{user:{id:123,role:admin}}Step8写 CookieSet-Cookie: next-auth.session-token...Step9后续请求Cookie ↓ JWT ↓ Session ↓ auth()完成身份恢复。8. 真正推荐的企业级写法很多人会这样session.useruser把整个数据库用户对象塞进去。这是不推荐的。更推荐jwt(){token.iduser.id token.roleuser.role}session(){session.user.idtoken.id session.user.roletoken.role}JWT 中只保留{id,role,tenantId}即Identity Claims而不是完整 User Profile这样Cookie 更小Session 更快权限控制更清晰用户资料更新不需要重新登录从架构角度看NextAuth 最核心的一句话是Provider ↓ User ↓ jwt() ↓ JWT(Token) ↓ session() ↓ Session ↓ auth() ↓ Middleware / Server Component / Client Component理解这条链路后基本就能看懂 NextAuth 80% 的源码和大部分企业项目中的认证实现。9. 具体例子我们用一个完整例子来理解。场景数据库里有一个用户{id:1001,name:Tom,email:tomgmail.com,role:admin,department:研发部,salary:50000}用户通过 Credentials 登录。第一步authorize()asyncauthorize(credentials){constuserawaitdb.user.findUnique(...)returnuser}此时得到{id:1001,name:Tom,email:tomgmail.com,role:admin,department:研发部,salary:50000}这个对象叫User第二步jwt()NextAuth 调用callbacks:{asyncjwt({token,user}){if(user){token.iduser.id token.roleuser.role}returntoken}}此时生成{id:1001,role:admin}这就是JWT PayloadJWT 被存入 CookieCookie next-auth.session-token ↓ { id: 1001, role: admin }为什么只存这两个字段因为 JWT 每次请求都要带浏览器 ↓ Cookie ↓ 服务器如果把全部用户信息塞进去{id:1001,name:Tom,email:...,department:...,salary:50000,...}Cookie 会越来越大。所以 JWT 通常只存身份标识 权限信息例如{id:1001,role:admin}第三步session()之后用户访问页面constsessionawaitauth()NextAuth 会Cookie ↓ 解析 JWT ↓ 执行 session()此时session({session,token})参数token{id:1001,role:admin}你决定给前端暴露什么asyncsession({session,token}){session.user.idtoken.id session.user.roletoken.rolereturnsession}得到{user:{id:1001,role:admin}}这就是Session前端能拿到的对象。为什么还要 Session因为JWT 服务端身份凭证 Session 前端用户信息JWT 是内部使用的。Session 是公开给 React 页面使用的。一个真实企业场景假设 JWT{id:1001,role:admin,tenantId:company-a,accessToken:xxxxx}这里accessToken可能是调用第三方系统的凭证。不能暴露给浏览器。因此asyncsession({session,token}){session.user.idtoken.id session.user.roletoken.rolereturnsession}前端拿到{user:{id:1001,role:admin}}而{accessToken:xxxxx}永远不会暴露。生命周期登录第一次authorize() ↓ user ↓ jwt() ↓ JWT生成以后每次请求Cookie ↓ JWT ↓ jwt() ← 再次执行 ↓ session() ↓ Session注意第一次登录jwt({token,user})user有值{id:1001,role:admin}第二次刷新页面jwt({token,user})此时userundefined因为用户已经登录了。NextAuth 直接从 Cookie 中恢复 JWT。所以常见写法asyncjwt({token,user}){if(user){token.iduser.id token.roleuser.role}returntoken}就是第一次登录 ↓ 把 User 写进 JWT 后续请求 ↓ 直接复用 JWT最终可以这样理解数据库用户(User) { id:1001, role:admin, salary:50000 } ↓ jwt() ↓ JWT(Token) { id:1001, role:admin } ↓ session() ↓ Session { user:{ id:1001, role:admin } }三者职责对象作用谁能访问User数据库完整用户服务端JWT(Token)身份凭证、权限声明服务端存于 CookieSession给前端展示的用户信息前端 服务端可以记一句口诀User 是原始数据 JWT 是身份证 Session 是展示给前端看的名片这也是为什么 NextAuth 的核心 Callback 永远是user ↓jwt()↓ token ↓session()↓ session本质上是在做一次用户对象 → 身份声明(Claims) → 前端可见数据(View Model)的转换。