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

如何在Next.js的Axios拦截器中处理Auth0令牌刷新失败后的用户登出

在Next.js + Auth0环境下优化Axios拦截器的令牌刷新失败处理

以下是针对你的需求优化后的Axios拦截器代码,重点解决令牌刷新失败时的自动登出与重定向问题,并附带最佳实践说明:

完整优化代码

import { AxiosInstance, isAxiosError } from 'axios';
import { Auth0Client } from '@auth0/auth0-react';

// 全局状态:控制令牌刷新并发,避免重复请求
let isAlreadyFetchingAccessToken = false;
// 订阅者队列:处理同时触发的多个请求
let subscribers: ((accessToken: string) => void)[] = [];

/**
 * 添加请求订阅者,等待新令牌获取后重试
 */
function addSubscriber(callback: (accessToken: string) => void) {
  subscribers.push(callback);
}

/**
 * 令牌刷新完成后通知所有订阅者
 */
function onAccessTokenFetched(accessToken: string) {
  subscribers.forEach(callback => callback(accessToken));
  subscribers = [];
}

/**
 * 调用Auth0刷新令牌接口
 */
async function refreshAccessToken() {
  try {
    const { data } = await axios.get(REFRESH_ACCESS_TOKEN_API);
    return data.access_token;
  } catch (error) {
    // 抛出错误,让拦截器处理登出逻辑
    throw error;
  }
}

/**
 * 配置Axios响应拦截器
 * @param axiosInstance Axios实例
 * @param auth0 Auth0客户端实例(从@auth0/auth0-react获取)
 */
export function setupResponseInterceptor(axiosInstance: AxiosInstance, auth0: Auth0Client) {
  axiosInstance.interceptors.response.use(
    response => response,
    async error => {
      const originalRequest = error.config;

      // 防止无限循环:标记已重试过的请求,不再处理
      if (originalRequest._retry) {
        throw error;
      }

      // 仅处理令牌过期导致的401错误
      if (isAxiosError(error) && error.response?.status === 401) {
        const isTokenExpired = error.response.data?.error === 'invalid_token' || 
                              error.response.data?.error_description?.includes('expired');
        
        // 非令牌过期的401(如权限不足)直接抛出错误
        if (!isTokenExpired) {
          throw error;
        }

        if (!isAlreadyFetchingAccessToken) {
          isAlreadyFetchingAccessToken = true;
          try {
            const newAccessToken = await refreshAccessToken();
            // 更新Auth0会话中的令牌
            await auth0.setSession({ access_token: newAccessToken });
            // 通知所有等待的请求重试
            onAccessTokenFetched(newAccessToken);
          } catch (err) {
            // 刷新失败:执行Auth0登出并重定向到首页
            await auth0.logout({
              logoutParams: {
                returnTo: window.location.origin
              }
            });
            // 清空订阅者队列,避免后续请求无效等待
            subscribers = [];
            return Promise.reject(err);
          } finally {
            // 重置刷新状态,允许后续请求触发刷新
            isAlreadyFetchingAccessToken = false;
          }
        }

        // 将当前请求加入订阅队列,等待新令牌
        return new Promise((resolve) => {
          addSubscriber((accessToken) => {
            originalRequest._retry = true;
            originalRequest.headers.Authorization = `Bearer ${accessToken}`;
            resolve(axiosInstance(originalRequest));
          });
        });
      }

      // 非401错误直接抛出
      throw error;
    },
  );
}

关键逻辑说明

  1. Auth0客户端注入
    拦截器需要接收Auth0的客户端实例(通过useAuth0钩子在客户端组件中获取),这样才能调用官方的logout和setSession方法,确保登出逻辑符合Auth0的会话管理规范。

  2. 避免无限循环
    给原始请求添加_retry标记,当请求重试后仍失败时,拦截器会直接抛出错误,不会重复进入刷新流程。

  3. 精准处理令牌过期
    通过判断响应的错误信息(如invalid_token或包含expired的描述),只处理令牌过期导致的401,避免误处理权限不足等其他场景的401错误。

  4. 刷新失败的登出流程
    当refreshAccessToken抛出错误时,立即调用auth0.logout,指定重定向到首页(window.location.origin),同时清空订阅者队列,重置刷新状态,避免无效资源占用。

  5. 并发请求管理
    使用订阅者模式处理多个同时触发的请求,确保所有请求都能在令牌刷新完成后,使用新令牌重试。

最佳实践

  • 客户端与服务器端区分处理
    如果你的Axios拦截器用于Next.js的服务器端代码(如API路由、getServerSideProps),需要使用@auth0/nextjs-auth0提供的destroySession方法处理登出,而不是客户端的logout。

  • 添加错误日志
    在登出前可以记录刷新失败的错误信息(如错误栈、请求URL),方便后续排查问题。

  • 用户友好提示
    登出前可以通过全局状态或Toast组件给用户显示提示(如"会话已过期,请重新登录"),提升用户体验。

  • 令牌存储同步
    刷新令牌后,务必调用auth0.setSession更新Auth0的会话存储,确保其他组件也能获取到新令牌。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 19:51:09