GraphQL Mesh中间件鉴权失败时如何触发客户端重定向
问题背景
采用GraphQL Mesh作为应用与API之间的网关,使用Apollo Client作为GraphQL客户端。用户点击登录按钮后访问首个页面时,会发起查询从CMS加载数据,该查询需经过网关处理。网关层添加了鉴权逻辑,校验用户是否持有有效的JWT访问令牌:若令牌无效,需将用户重定向回登录页;若令牌有效,则放行请求。
原有实现代码
网关层鉴权解析器(is-auth.ts)
const header = context.headers.authorization; if (typeof header === "undefined") { return new Error("Unauthorized: no access token found."); } else { const token = header.split(" ")[1]; if (token) { try { const user = jwt.verify(token, process.env.JWT_SECRET as string); } catch (error) { return new Error("Unauthorized: " + error); } } else { return new Error("Unauthorized: no access token found."); } } return next(root, args, context, info);
客户端重定向逻辑
const { data, error } = await apolloClient(accessToken).query({ query: gql` query { ...where my query is. } `, }); if (error) { return { redirect: { permanent: false, destination: `/sign-in`, }, }; }
现存问题
在网关鉴权解析器中直接返回Error对象时,Apollo Client无法按预期在返回值中接收到error对象触发重定向逻辑,Error会被直接抛出,导致用户端直接展示错误页面,不符合业务预期。
可行解决方案
- 修正网关层错误抛出逻辑:GraphQL解析器规范中,鉴权失败场景需要主动
throw错误而非return Error对象,同时给错误添加标准化的扩展字段,标记错误类型为未授权,配合设置对应HTTP 401状态码,同时避免透传JWT原始错误信息防止安全风险。修正后的鉴权代码如下:
const header = context.headers.authorization; // 统一校验请求头格式 if (!header || !header.startsWith("Bearer ")) { throw new Error("Unauthorized", { extensions: { code: "UNAUTHENTICATED", http: { status: 401 } } }); } const token = header.split(" ")[1]; if (!token) { throw new Error("Unauthorized", { extensions: { code: "UNAUTHENTICATED", http: { status: 401 } } }); } try { const user = jwt.verify(token, process.env.JWT_SECRET as string); // 校验通过后将用户信息挂载到上下文,供后续下游解析器使用 context.user = user; } catch (error) { throw new Error("Invalid access token", { extensions: { code: "UNAUTHENTICATED", http: { status: 401 } } }); } return next(root, args, context, info);
- 配置Apollo Client全局错误拦截链路:避免在每个请求处零散判断错误,统一在Apollo链路层拦截鉴权失败错误,自动触发重定向逻辑,防止漏判导致错误页展示。配置示例如下:
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; // 鉴权错误拦截链路 const authErrorLink = onError(({ graphQLErrors, networkError }) => { // 识别所有未授权类型错误:包括GraphQL层标记的UNAUTHENTICATED错误、HTTP层返回的401错误 const hasAuthFailure = graphQLErrors?.some(err => err.extensions?.code === 'UNAUTHENTICATED') || networkError?.statusCode === 401; if (hasAuthFailure) { // 清理本地存储的失效凭证 localStorage.removeItem('accessToken'); // 重定向到登录页 window.location.replace('/sign-in'); } }); const httpLink = createHttpLink({ uri: '/graphql', // 替换为实际的Mesh网关地址 }); // 导出客户端创建方法 export const createApolloClient = (accessToken) => new ApolloClient({ link: from([authErrorLink, httpLink]), cache: new InMemoryCache(), headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {} });
- 适配SSR场景下的错误捕获:如果是在Next.js等框架的服务端渲染数据获取逻辑中调用query,Apollo Client请求出错时会直接抛出异常,不会把错误挂载到返回值上,原有代码缺少
try/catch包裹才会导致错误冒泡触发默认错误页。修正后的服务端数据获取逻辑如下:
export async function getServerSideProps(context) { const accessToken = context.req.cookies.accessToken; try { const { data } = await apolloClient(accessToken).query({ query: gql` query { ...where my query is. } `, }); return { props: { data } }; } catch (err) { // 识别鉴权失败错误 const isAuthFailure = err.graphQLErrors?.some(e => e.extensions?.code === 'UNAUTHENTICATED') || err.networkError?.statusCode === 401; if (isAuthFailure) { return { redirect: { permanent: false, destination: `/sign-in`, }, }; } // 非鉴权错误走默认错误处理逻辑 throw err; } }
说明:GraphQL Mesh会自动识别解析器抛出错误中
extensions.http字段的配置,设置对应HTTP响应状态码,不需要在网关层处理重定向逻辑,重定向统一放在客户端/SSR层实现即可,符合GraphQL接口的设计规范。
内容的提问来源于stack exchange,提问作者Herman Vulkers
相关产品推荐
相关产品推荐

