Next.js Authentication、Session 与 Authorization 边界
把 Next.js App Router 中登录/注册、Server Action、服务器 Session、Client Session、DAL 与资源级 Authorization 串成一条完整信任链。
[!info] related notes
- 所属 MOC: nextjs-moc
- 相关概念:
- 易混淆概念:
- 相关资源:
Next.js Authentication、Session 与 Authorization 边界
一张总图
Next.js 中认证相关代码容易散落在 Form、Server Action、Auth.js、Cookie、Server Component 和 Client Context 中。正确做法是先按 trust boundary 分层:
Login / Signup Form
↓ untrusted input
Server Action
↓ validate credentials/form
Identity / User Service
↓
Auth provider / DB
↓
Session created
↓
future request carries credential
↓
server reads current session
↓
Authorization / DAL / Policy
↓
Resource query or mutation
注册流程
注册不是 Auth.js Provider 的同义词。典型注册链:
FormData
→ runtime validation
→ normalize email/username
→ duplicate check
→ password hash
→ user insert/transaction
→ create session or sign in
→ redirect
客户端“用户名可用”检查只改善 UX;数据库 UNIQUE constraint 和服务端处理才是最终一致性边界。
登录流程
credentials
→ runtime validation
→ lookup identity
→ password / provider verification
→ establish session
→ redirect / render authenticated UI
如果使用 Auth.js,Provider 与 session plumbing 可以由库承担;用户表业务、密码策略和资源授权仍是应用职责。
Server Session 与 Client Session
Server Session
Server Component、Server Action、Route Handler 可以在服务器读取当前 session / identity:
request credential
→ server auth/session lookup
→ trusted identity input
它适合:
- 服务端数据访问
- Server Action 授权
- Route Handler 授权
- 服务端条件渲染
Client Session
Client Component 可以通过 Provider/Hook 得到 session projection,用于:
- 显示头像
- 登录/退出 UI
- 客户端交互
- 某些 client-side fetch identity convenience
但:
Client Session
≠ permission proof
客户端数据永远可以被绕过或伪造;敏感动作必须重新走服务端授权。
为什么 Auth Check 应靠近数据边界
只在顶层 Layout 做:
if !session → redirect
不等于下面所有 Server Action / Route Handler 都安全。
应用有多个入口:
Page
Server Action
Route Handler
Direct HTTP call
所以安全检查应靠近真正的 data/mutation boundary:
Server Action
→ auth()
→ authorize resource
→ mutate
或集中在 server-only DAL / policy 中复用。
DAL 的作用
Data Access Layer 可以把“读取身份 + 最小用户信息 + 资源访问规则”集中起来:
Page / Action / Handler
→ verifySession()
→ authorized data function
→ DB / service
DAL 的价值不是为了制造更多层,而是避免每个调用点自行拼 auth check,最终漏掉一个入口。
Authentication 与 Authorization
Authentication
→ 你是谁?
Session
→ 后续请求如何重新得到这个身份?
Authorization
→ 当前身份能否执行这个具体操作?
例子:
session.user.id = u1
Task.ownerId = u2
Authentication 成功,但 u1 仍不能修改 u2 的 Task。
Protecting Server Actions
Server Action 在服务器运行,但参数来自客户端:
'use server'
≠ trusted caller
典型链:
FormData / args
→ validate
→ authenticate
→ authorize
→ business rule
→ mutation
UI 隐藏按钮只是体验,不是权限控制。
SessionProvider 的边界
Client Provider 只影响 Client Component tree。Server Components 在服务器执行,不能依赖客户端 Context 作为身份来源。
因此:
Server Component
→ server auth/session API
Client Component
→ SessionProvider / useSession when necessary
不要为了让 Server Component“看到 Context”而把整棵应用改成 Client Component。
最短记忆
Signup = create identity + optionally establish session
Login = verify identity + establish session
Session = carry auth state across requests
Authorization = resource/action decision
Server Action must authz itself
Client Session is UI projection
Auth check should live near data/mutation boundary