当前位置: 首页 > news >正文

WHAT - NextAuth 登录流程架构

文章目录

  • 1. Provider:认证入口
  • 2. authorize()
  • 3. jwt() Callback
    • 常见写法
  • 4. session() Callback
    • 为什么要两层?
  • 5. auth()
  • 6. Middleware
    • RBAC 权限控制
  • 7. 登录全过程源码视角
    • Step1
    • Step2
    • Step3
    • Step4
    • Step5
    • Step6
    • Step7
    • Step8
    • Step9
  • 8. 真正推荐的企业级写法
  • 9. 具体例子
    • 场景
    • 第一步:authorize()
    • 第二步:jwt()
    • 第三步:session()
    • 为什么还要 Session?
    • 一个真实企业场景
    • 生命周期
    • 最终可以这样理解

在 WHAT - NextAuth 权限认证机制 中我们介绍了 NextAuth 是什么以及优缺点。下一步,最重要的是理解它内部的数据流。

可以把 NextAuth 看成一个认证状态机:

┌─────────────┐ │ Provider │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ authorize │ └──────┬──────┘ │ User ▼ ┌─────────────┐ │ callbacks │ │ jwt() │ └──────┬──────┘ │ Token ▼ ┌─────────────┐ │ callbacks │ │ session() │ └──────┬──────┘ │ Session ▼ ┌─────────────┐ │ auth() │ │ useSession()│ └──────┬──────┘ │ ▼ ┌─────────────┐ │ Middleware │ └─────────────┘

1. Provider:认证入口

Provider 决定用户如何登录。例如 GitHub:

providers:[GitHub({clientId,clientSecret})]

Credentials:

providers:[Credentials({asyncauthorize(credentials){...}})]

本质上:

Provider = 获取用户身份信息

不同 Provider 获取方式不同:

Provider获取用户方式
GitHubOAuth
GoogleOAuth
Credentials用户名密码
EmailMagic Link

2. authorize()

只有 Credentials Provider 有这个步骤。例如:

Credentials({asyncauthorize(credentials){constuser=awaitdb.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。


常见写法

把数据库字段塞进 JWT:

callbacks:{asyncjwt({token,user}){if(user){token.id=user.id token.role=user.role}returntoken}}

结果:

{"sub":"1","id":"1","role":"admin"}

JWT 会被加密后放到 Cookie。

Cookie ↓ JWT ↓ role=admin

4. session() Callback

客户端拿不到 JWT。客户端拿的是 Session。

callbacks:{asyncsession({session,token}){session.user.id=token.id session.user.role=token.rolereturnsession}}

流程:

JWT ↓ session() ↓ Session

例如:

JWT:

{"id":"1","role":"admin"}

Session:

{"user":{"id":"1","role":"admin"}}

为什么要两层?

因为:

JWT = 服务端真实身份 Session = 暴露给前端的数据

你可以过滤敏感信息:

session.user.role=token.role// 不暴露token.accessToken token.refreshToken

5. auth()

App Router 新版本最重要的 API。

constsession=awaitauth()

内部其实在做:

Cookie ↓ 读取 JWT ↓ 解密 JWT ↓ 执行 session() ↓ 返回 Session

所以:

constsession=awaitauth()

相当于:

constuser=getCurrentUser()

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/github

Step2

重定向 GitHub

GitHub OAuth

用户授权:

Allow

Step3

GitHub 返回 code

callback?code=xxx

Step4

NextAuth 交换 Token

code ↓ access_token

调用 GitHub API:

GET /user

获得:

{"id":123,"login":"tom"}

Step5

生成 User

内部统一转换:

user={id:"123",name:"Tom"}

Step6

执行 jwt()

jwt({token,user})

得到:

{"id":"123","role":"admin"}

Step7

生成 Session

session({session,token})

得到:

{"user":{"id":"123","role":"admin"}}

Step8

写 Cookie

Set-Cookie: next-auth.session-token=...

Step9

后续请求

Cookie ↓ JWT ↓ Session ↓ auth()

完成身份恢复。


8. 真正推荐的企业级写法

很多人会这样:

session.user=user

把整个数据库用户对象塞进去。

这是不推荐的。更推荐:

jwt(){token.id=user.id token.role=user.role}session(){session.user.id=token.id session.user.role=token.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":"tom@gmail.com","role":"admin","department":"研发部","salary":50000}

用户通过 Credentials 登录。


第一步:authorize()

asyncauthorize(credentials){constuser=awaitdb.user.findUnique(...)returnuser}

此时得到:

{"id":"1001","name":"Tom","email":"tom@gmail.com","role":"admin","department":"研发部","salary":50000}

这个对象叫:

User

第二步:jwt()

NextAuth 调用:

callbacks:{asyncjwt({token,user}){if(user){token.id=user.id token.role=user.role}returntoken}}

此时生成:

{"id":"1001","role":"admin"}

这就是:

JWT Payload

JWT 被存入 Cookie:

Cookie 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()

之后用户访问页面:

constsession=awaitauth()

NextAuth 会:

Cookie ↓ 解析 JWT ↓ 执行 session()

此时:

session({session,token})

参数:

token={"id":"1001","role":"admin"}

你决定给前端暴露什么:

asyncsession({session,token}){session.user.id=token.id session.user.role=token.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.id=token.id session.user.role=token.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})

此时:

user===undefined

因为用户已经登录了。NextAuth 直接从 Cookie 中恢复 JWT。

所以常见写法:

asyncjwt({token,user}){if(user){token.id=user.id token.role=user.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)身份凭证、权限声明服务端(存于 Cookie)
Session给前端展示的用户信息前端 + 服务端

可以记一句口诀:

User 是原始数据 JWT 是身份证 Session 是展示给前端看的名片

这也是为什么 NextAuth 的核心 Callback 永远是:

user ↓jwt()↓ token ↓session()↓ session

本质上是在做一次用户对象 → 身份声明(Claims) → 前端可见数据(View Model)的转换。

http://www.jsqmd.com/news/963623/

相关文章:

  • 2026沈阳旧金回收测评!高诚意无套路,收的顶品牌强势夺魁 - 奢侈品回收评测
  • 合肥购宠全攻略|江淮梅雨湿冷气候避坑指南 + 伴西西双门店精选 5 家正规宠物店 - 资讯速览
  • 别再瞎点Debug了!ZYNQ SDK与PL联合调试的保姆级流程(含ILA触发条件详解)
  • 2026 青岛家里有老酒/名酒/茅台酒/礼品闲置别乱卖!青岛本地实体回收店真实打分测评 - 资讯速览
  • 力扣HOT100(55)多维动态规划 - 编辑距离
  • 三步快速上手:如何轻松搭建专业级H5可视化编辑器
  • 2026年工业搅拌机实力生产厂家甄选:电池材料/化工/砂浆/粉体搅拌机制造商及高效盘条式、无重力混合机专业企业解析 - 品牌企业推荐师(官方)
  • 2026年,Claude Code 凭什么成了程序员的第一终端?深度拆解 Anthropic 的 Agentic 编程革命
  • 夸克网盘批量管理终极指南:3分钟掌握高效文件处理技巧
  • 基于BRF6150与TLV320AIC23B的蓝牙耳机系统设计与VxWorks协议栈实现
  • lodash 数组的常用做法
  • 一键备份你的QQ空间青春记忆:GetQzonehistory完整导出工具指南
  • QT自定义控件之热换站远程监控系统
  • 如何在本地构建千万级图片搜索引擎:ImageSearch实战指南
  • 哈夫曼树的简单介绍
  • 如何选择远心镜头内同轴光源和外同轴光源
  • OpenAI Codex 使用指南:程序员进入 AI Agent 编程时代
  • 2026实测南京黄金回收市场,禹竞深耕本地多年,口碑和实力双在线 - 奢侈品交易观察员
  • 福象商标宝 AI 综合型商标交易平台能力观察:从资质合规到授权过户全解析 - 资讯速览
  • 西门子博图比较指令的‘隐藏’技巧与常见坑点:从数据类型匹配到VARIANT使用避坑指南
  • 沈阳购宠全攻略|东北严寒大风气候避坑指南 + 伴西西浑南、沈河双直营店精选 5 家正规门店 - 资讯速览
  • GHelper终极指南:华硕笔记本轻量级控制神器,告别Armoury Crate卡顿烦恼
  • D2DX宽屏补丁:让暗黑破坏神2在现代PC上完美运行的终极指南
  • 高性价比一键生成论文工具势力榜(2026 实测推荐)
  • 大模型“睡眠”机制:提升推理能力,训练成本却线性增长?
  • 5分钟搞定百度网盘批量转存:免费开源神器BaiduPanFilesTransfers终极指南
  • 全国染料厂主要分布在哪些地区?产区分布与产能观察
  • 3分钟快速制作专业MDX词典:AutoMdxBuilder完全指南
  • 紧急通知:CSDN 2024Q3起强制启用「优质内容优先分发」新策略(附老作者迁移避坑清单)
  • 双51内核MCU通用实验板设计:兼容AT89S51与STC89C51的硬件平台