在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
相关产品推荐
相关产品推荐

