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

在Next.js 13的Apollo Client中处理认证令牌过期问题

在Next.js 13中用Apollo Client处理认证令牌过期问题

要处理令牌过期,我们可以通过拦截Apollo Client的响应错误,判断是否为令牌失效(通常对应401状态码或UNAUTHENTICATED GraphQL错误码),然后执行刷新令牌逻辑、重试原请求;如果刷新失败则清除本地令牌并跳转登录页。以下是具体实现方案:

完整修改后的代码

"use client";

import React from "react";
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  HttpLink,
  ApolloLink,
  from,
  Observable,
} from "@apollo/client";
import { useRouter } from "next/navigation";

// 刷新令牌的函数,需根据你的后端接口调整实现
const refreshAuthToken = async () => {
  const refreshToken = localStorage.getItem("RefreshToken");
  if (!refreshToken) throw new Error("无可用刷新令牌");

  // 示例:用REST接口刷新令牌,可替换为你的GraphQL查询逻辑
  const response = await fetch(`${process.env.NEXT_PUBLIC_SANDBOX_URL}/refresh-token`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ refreshToken }),
  });

  if (!response.ok) throw new Error("刷新令牌失败");
  
  const data = await response.json();
  // 更新本地存储的令牌
  localStorage.setItem("AuthToken", data.newAuthToken);
  localStorage.setItem("RefreshToken", data.newRefreshToken);
  return data.newAuthToken;
};

const httpLink = new HttpLink({ uri: process.env.NEXT_PUBLIC_SANDBOX_URL });

const authMiddleware = new ApolloLink((operation, forward) => {
  // 给请求添加认证头
  operation.setContext(({ headers = {} }) => ({
    headers: {
      ...headers,
      authorization: localStorage.getItem("AuthToken") || null,
    },
  }));
  return forward(operation);
});

// 错误处理链路:拦截令牌过期错误并处理
const errorLink = new ApolloLink((operation, forward) => {
  return new Observable((observer) => {
    forward(operation).subscribe({
      next: observer.next.bind(observer),
      error: async (error) => {
        // 判断是否为令牌过期导致的认证失败
        const isTokenExpired = 
          error.graphQLErrors?.some(err => err.extensions?.code === "UNAUTHENTICATED") || 
          error.networkError?.statusCode === 401;

        if (isTokenExpired) {
          try {
            // 刷新令牌
            const newAuthToken = await refreshAuthToken();
            // 更新当前请求的认证头
            operation.setContext(({ headers = {} }) => ({
              headers: {
                ...headers,
                authorization: newAuthToken,
              },
            }));
            // 重试原请求
            forward(operation).subscribe({
              next: observer.next.bind(observer),
              error: observer.error.bind(observer),
              complete: observer.complete.bind(observer),
            });
          } catch (refreshError) {
            // 刷新失败:清除本地令牌并跳转登录页
            localStorage.removeItem("AuthToken");
            localStorage.removeItem("RefreshToken");
            const router = useRouter();
            router.push("/login");
            observer.error(refreshError);
          }
        } else {
          // 非令牌过期错误直接抛出
          observer.error(error);
        }
      },
      complete: observer.complete.bind(observer),
    });
  });
});

export const client = new ApolloClient({
  // 组合链路顺序:错误处理 -> 认证中间件 -> HTTP链路
  link: from([errorLink, authMiddleware, httpLink]),
  cache: new InMemoryCache(),
});

const ApolloClientProvider = ({ children }) => {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export default ApolloClientProvider;

关键细节说明

  • 令牌刷新逻辑:refreshAuthToken需要和你的后端接口适配,如果后端用GraphQL提供刷新服务,替换为对应的GraphQL查询即可。
  • 避免重复刷新:如果多个请求同时触发401,可添加全局刷新锁减少重复请求:
    let refreshPromise: Promise<string> | null = null;
    const refreshAuthToken = async () => {
      if (refreshPromise) return refreshPromise;
      refreshPromise = (async () => {
        // 原刷新逻辑
      })().finally(() => {
        refreshPromise = null;
      });
      return refreshPromise;
    };
    
  • 错误判断适配:不同后端返回的认证失败标识可能不同,需根据实际情况调整isTokenExpired的判断条件。
  • 登录跳转:使用Next.js App Router的useRouter完成跳转,确保在客户端组件中正常运行。

内容的提问来源于stack exchange,提问作者Santhosh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:00:59