GraphQL Auth Context
GraphQL Auth Context 把请求级可信身份与 request-scoped 依赖注入 Resolver;Authentication 回答“你是谁”,资源级 Authorization 仍应由 Service / Policy 结合业务数据判断。
[!info] related notes
- 所属 MOC: graphql-moc
- 前置概念: graphql-query-and-resolver
- 并列概念: server-side-session-authentication、access-token
- 易混淆概念: graphql-schema
- 关系笔记: graphql-n-plus-one
GraphQL Auth Context
一句话定义
GraphQL Context 是一次 operation 执行期间共享的请求级上下文;认证常把已验证身份放进 Context,而资源级授权仍应在真正访问资源的 Service / Policy 边界判断。
典型链路
HTTP request
↓ cookie / Authorization header
Authentication
↓ 得到可信 principal
GraphQL context
↓
Resolver
↓
Service / Policy authorization
↓
Repository / Database
例如:
context: async ({ req }) => {
const user = await authenticate(req)
return {
currentUser: user,
services,
loaders: createRequestLoaders(),
}
}
Resolver:
Query: {
assessment: async (_p, { id }, ctx) => {
if (!ctx.currentUser) {
throw new Error('UNAUTHENTICATED')
}
return ctx.assessmentService.get({
assessmentId: id,
actorId: ctx.currentUser.id,
})
},
}
Service / Policy 再判断:
actorId = 42
assessmentId = 789
↓
读取资源 / ownership / organization / status
↓
User 42 是否允许读取 Assessment 789?
Authentication 与 Authorization
必须分开:
Authentication
= 你是谁?
Authorization
= 你能不能访问这个具体资源/字段?
因此一个非常实用的职责划分是:
Context
→ “当前请求是谁?”
Service / Policy
→ “这个人能不能对 Assessment 789 做这件事?”
Schema 里存在:
type Query {
assessment(id: ID!): Assessment
}
只说明这个字段属于 API contract,不说明任何登录用户都能读取任意 assessment(id)。
为什么资源级 Authorization 不适合只放 Context
资源权限往往依赖实时业务数据:
assessment.user_id
organization membership
resource status
share permission
role / policy
Context 本身通常只携带 request facts,例如 currentUser.id = 42,它不会天然知道 Assessment 789 的 ownership。
所以不要把:
ctx.user.id === assessment.userId
这类业务授权逻辑散落在每个 Resolver 或 Context builder 里。
更好的设计:
Resolver
→ 把 actorId + resourceId 交给 Service
→ Service / Policy 读取必要事实并统一授权
这也让 REST、GraphQL、Job、CLI 等不同入口共享同一套授权规则。
为什么 GraphQL 特别容易出现授权遗漏
REST 常在 route/controller 粒度做权限检查;GraphQL 的字段可以被从不同 Query 路径组合出来:
Query.user → assessments
Query.assessment → owner
Query.me → latestAssessment
如果只在某个根 Query 做授权,而字段 resolver / service 没有资源 ownership 约束,同一敏感数据可能通过另一条路径被取出。
这就是 GraphQL 里的 BOLA/IDOR 风险形态之一。
Context 应放什么
适合:
- authenticated identity / principal;
- request id / trace id;
- request-scoped DataLoaders;
- service dependencies;
- locale 等请求级事实。
不适合:
- 可跨用户共享的可变授权状态;
- 未验证 token 原文作为“已认证身份”;
- 把所有业务状态都塞进 context;
- 用 context 绕过 Service 层的资源权限规则;
- 全局共享带用户数据的 DataLoader cache。
Context 与 DataLoader 的联系
DataLoader 常放在 Context,不是因为它属于 Authentication,而是因为两者都具有 request scope:
一次 GraphQL Request
↓
Context
├─ principal
├─ services
└─ loaders
↓
本请求所有 Resolver 共享
请求结束后,这些 request-scoped loader memoization 应一起丢弃,避免跨请求数据污染。
推荐边界
HTTP middleware / Auth boundary
→ 验证 credential、生成 request facts
GraphQL Context
→ 携带可信 identity + request-scoped dependencies
Resolver
→ GraphQL API adapter,传递 actor/resource 参数
Service / Domain Policy
→ 最终判断是否允许访问/修改业务资源
Repository / Database
→ 提供授权和业务判断所需事实
核心判断
GraphQL Schema 约束的是“这个字段能不能被问”,Context 说明“当前请求是谁”,Service / Policy 决定“这个调用者能不能得到或修改这个真实资源”。
三个问题不能混成一个。