You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:12:40