GraphQL Yoga认证失败时自定义返回状态码问题求助
我之前也踩过这个坑——GraphQL默认不管业务逻辑错误都返回200,确实和我们熟悉的REST状态码习惯不符。针对你用withAuth高阶函数做校验的场景,有两个优雅的解决方案,不用再纠结Express中间件的局限性:
方案一:自定义错误+响应拦截器修改状态码
核心思路是在withAuth里抛出带标识的自定义错误,再通过Yoga自带的响应拦截器,根据错误标识动态修改状态码。
步骤1:修改withAuth,抛出带扩展字段的错误
在你的withAuth高阶函数中,当检测到用户未登录时,抛出一个包含extensions字段的Error,明确标记我们需要的状态码:
const withAuth = (resolver) => async (parent, args, context, info) => { if (!context.user) { throw new Error('用户未登录,请先登录') { extensions: { code: 'UNAUTHENTICATED', statusCode: 403 // 自定义状态码 } }; } return resolver(parent, args, context, info); };
步骤2:配置GraphQL Yoga的responseInterceptor
创建Yoga实例时,添加responseInterceptor选项,检查响应中的错误,如果存在我们定义的statusCode,就修改响应状态码:
import { createYoga } from 'graphql-yoga'; import { schema } from './schema'; const yoga = createYoga({ schema, responseInterceptor: async (response, context) => { // 检查响应错误中是否包含自定义状态码 if (response.errors?.[0]?.extensions?.statusCode) { context.res.status(response.errors[0].extensions.statusCode); } return response; } }); // 挂载到Express app.use('/graphql', yoga);
这样当withAuth抛出未登录错误时,响应状态码会自动改成403,同时错误信息也会按GraphQL规范返回。
方案二:用Envelop插件提前拦截认证(更推荐)
GraphQL Yoga基于Envelop构建,我们可以写一个Envelop插件,在查询执行前就完成认证校验,更早拦截未登录请求,直接设置状态码,无需等到resolver执行阶段。
实现认证Envelop插件
const authPlugin = { onExecute({ args, context }) { // 这里根据业务逻辑判断当前操作是否需要认证 // 比如检查操作名称、或者schema中定义的@auth指令 const isProtectedOperation = checkIfOperationNeedsAuth(args.operationName, args.document); if (isProtectedOperation && !context.user) { // 直接设置状态码并抛出错误 context.res.status(403); throw new Error('用户未登录,请先登录') { extensions: { code: 'UNAUTHENTICATED', statusCode: 403 } }; } } }; // 辅助函数示例:判断操作是否需要认证 function checkIfOperationNeedsAuth(operationName, document) { // 比如你可以维护一个需要认证的操作名单,或者解析schema中的@auth指令 const protectedOperations = ['createPost', 'updateUser']; return protectedOperations.includes(operationName); }
在Yoga中注册插件
const yoga = createYoga({ schema, plugins: [authPlugin] });
这个方案的优势是校验时机更早,避免了不必要的resolver执行,还能通过schema指令(比如@auth)更灵活地标记需要保护的操作,不用给每个resolver都套withAuth。
为什么Express中间件不好用?
你提到的问题确实存在:所有GraphQL请求都POST到同一个端点,中间件只能拿到请求body,要判断具体调用的查询/突变,需要提前解析GraphQL查询内容、操作名称甚至字段,这会大幅增加中间件复杂度,远不如直接用Yoga/Envelop提供的钩子贴合GraphQL执行流程。
内容的提问来源于stack exchange,提问作者Adam

