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

非React组件下Axios拦截器集成MSAL的交互问题解决方案咨询

问题

我使用Axios发送请求,通过MSAL实现身份认证。为避免在每个请求中手动附加Access Token,采用Axios拦截器进行全局处理。由于该代码不属于React组件,无法使用MSAL Hooks,因此我导出了传入MsalProvider的PublicClientApplication实例。

我不确定此方案是否为最优解,且当前实现无法处理需要交互的场景(已注释掉相关重定向代码)。根据微软官方文档,在React组件外调用acquireTokenSilent失败时,不应回退到交互操作,所有交互需在MsalProvider组件树内完成。若移除注释代码则缺失交互处理能力,请问有哪些可行的替代实现方式?

当前代码如下:

import { InteractionRequiredAuthError } from '@azure/msal-browser'
import axios from 'axios'
import { azureAdInstance } from '../index'

const axiosInstance = axios.create({
    baseURL: "https://myapi.com",
})

axiosInstance.interceptors.request.use(async (request) => {
    const instance = azureAdInstance
    const accounts = azureAdInstance.getAllAccounts()

    const accessTokenRequest = {
        scopes: ["scope1"],
        account: accounts[0],
    }

    // 静默获取Access Token并附加到请求中
    try {
        const { accessToken } = await instance.acquireTokenSilent(
            accessTokenRequest
        )
        if (accessToken) request.headers.Authorization = `Bearer ${accessToken}`
    } catch (error) {
        // 根据微软文档不应使用
        // if (error instanceof InteractionRequiredAuthError) {
        //    instance.acquireTokenRedirect(accessTokenRequest)
        // }
        console.log(error)
    }

    return request
})

export default axiosInstance
可行的替代实现方式

方案1:将交互逻辑委托给React组件内的MSAL回调

核心思路是在拦截器捕获InteractionRequiredAuthError时,通过事件触发或状态通知,让MsalProvider树内的组件处理交互请求,完成后再重试原请求。

实现步骤

  1. 搭建一个简单的全局事件总线,用于传递认证交互需求和请求缓存信息。
  2. 在Axios拦截器中捕获交互错误时,触发事件并缓存当前请求,返回一个pending状态的Promise等待后续处理。
  3. 在MsalProvider包裹的组件中监听事件,调用MSAL的交互方法(如acquireTokenPopup)完成认证。
  4. 交互成功获取token后,重新发起缓存的请求。

代码示例

1. 全局事件总线

// authEventBus.js
const eventBus = {
  on(event, callback) {
    document.addEventListener(event, (e) => callback(e.detail));
  },
  emit(event, data) {
    document.dispatchEvent(new CustomEvent(event, { detail: data }));
  },
};

export default eventBus;

2. 修改Axios拦截器

import { InteractionRequiredAuthError } from '@azure/msal-browser'
import axios from 'axios'
import { azureAdInstance } from '../index'
import eventBus from './authEventBus'

const axiosInstance = axios.create({
    baseURL: "https://myapi.com",
})

// 缓存需要重试的请求
let pendingRequests = [];

axiosInstance.interceptors.request.use(async (request) => {
    const instance = azureAdInstance
    const accounts = azureAdInstance.getAllAccounts()

    // 无登录账号时触发登录交互
    if (!accounts.length) {
      eventBus.emit('auth:require-login', { request });
      return new Promise((resolve) => {
        pendingRequests.push({ resolve, request });
      });
    }

    const accessTokenRequest = {
        scopes: ["scope1"],
        account: accounts[0],
    }

    try {
        const { accessToken } = await instance.acquireTokenSilent(accessTokenRequest)
        if (accessToken) request.headers.Authorization = `Bearer ${accessToken}`
        return request
    } catch (error) {
        if (error instanceof InteractionRequiredAuthError) {
          // 触发交互获取token的事件
          eventBus.emit('auth:require-interaction', { 
            request, 
            tokenRequest: accessTokenRequest 
          });
          return new Promise((resolve) => {
            pendingRequests.push({ resolve, request });
          });
        }
        console.log(error)
        return Promise.reject(error)
    }
})

// 供组件调用的重试方法
export const retryPendingRequests = (accessToken) => {
  pendingRequests.forEach(({ resolve, request }) => {
    if (accessToken) {
      request.headers.Authorization = `Bearer ${accessToken}`;
    }
    resolve(request);
  });
  pendingRequests = [];
};

export default axiosInstance

3. 在MsalProvider内添加交互处理组件

// AuthHandler.jsx
import { useMsal } from '@azure/msal-react'
import { useEffect } from 'react'
import eventBus from './authEventBus'
import { retryPendingRequests } from './axiosInstance'

const AuthHandler = () => {
  const { instance, accounts } = useMsal()

  useEffect(() => {
    // 处理未登录场景
    const handleLoginRequired = async ({ request }) => {
      if (!accounts.length) {
        await instance.loginPopup({ scopes: ["scope1"] });
        const { accessToken } = await instance.acquireTokenSilent({
          scopes: ["scope1"],
          account: instance.getAllAccounts()[0]
        });
        retryPendingRequests(accessToken);
      }
    }

    // 处理需要交互获取token的场景
    const handleInteractionRequired = async ({ tokenRequest }) => {
      try {
        const result = await instance.acquireTokenPopup(tokenRequest);
        retryPendingRequests(result.accessToken);
      } catch (err) {
        console.error('交互认证失败:', err);
        pendingRequests = [];
      }
    }

    eventBus.on('auth:require-login', handleLoginRequired);
    eventBus.on('auth:require-interaction', handleInteractionRequired);

    return () => {
      document.removeEventListener('auth:require-login', handleLoginRequired);
      document.removeEventListener('auth:require-interaction', handleInteractionRequired);
    }
  }, [instance, accounts])

  return null;
}

export default AuthHandler

4. 在App中引入该组件(需在MsalProvider内部)

// App.jsx
import { MsalProvider } from '@azure/msal-react'
import { azureAdInstance } from './index'
import AuthHandler from './AuthHandler'
import YourRoutes from './Routes'

function App() {
  return (
    <MsalProvider instance={azureAdInstance}>
      <AuthHandler />
      <YourRoutes />
    </MsalProvider>
  )
}

export default App

方案2:封装Axios实例为React Hook

如果请求大多在React组件内发起,可以将Axios实例封装成Hook,利用MSAL Hooks在组件内直接处理token获取和交互逻辑,避免在非组件代码中处理认证。

代码示例

// useAxios.js
import { useMsal } from '@azure/msal-react'
import axios from 'axios'
import { useEffect, useMemo } from 'react'

const useAxios = () => {
  const { instance, accounts } = useMsal()
  const axiosInstance = useMemo(() => axios.create({ baseURL: "https://myapi.com" }), [])

  useEffect(() => {
    const requestInterceptor = axiosInstance.interceptors.request.use(async (request) => {
      if (!accounts.length) return Promise.reject(new Error('未登录'))

      const accessTokenRequest = {
        scopes: ["scope1"],
        account: accounts[0]
      }

      try {
        const { accessToken } = await instance.acquireTokenSilent(accessTokenRequest)
        request.headers.Authorization = `Bearer ${accessToken}`
        return request
      } catch (error) {
        if (error instanceof InteractionRequiredAuthError) {
          // 直接在组件内调用交互方法
          const result = await instance.acquireTokenPopup(accessTokenRequest)
          request.headers.Authorization = `Bearer ${result.accessToken}`
          return request
        }
        return Promise.reject(error)
      }
    })

    return () => {
      axiosInstance.interceptors.request.eject(requestInterceptor)
    }
  }, [instance, accounts, axiosInstance])

  return axiosInstance
}

export default useAxios

组件内使用

// MyComponent.jsx
import useAxios from './useAxios'

const MyComponent = () => {
  const axiosInstance = useAxios()

  const fetchData = async () => {
    try {
      const response = await axiosInstance.get('/data')
      console.log(response.data)
    } catch (err) {
      console.error(err)
    }
  }

  return <button onClick={fetchData}>获取数据</button>
}

export default MyComponent

方案3:利用MSAL内置拦截器

MSAL浏览器库提供了addInterceptor方法,可以自动为请求附加token,交互逻辑可委托给组件内处理(适合以fetch发起请求的场景,Axios需额外适配)。

代码示例(MSAL初始化处)

// index.js
import { PublicClientApplication, InteractionRequiredAuthError } from '@azure/msal-browser'
import { MsalProvider } from '@azure/msal-react'
import eventBus from './authEventBus'
import { retryPendingRequests } from './axiosInstance'

export const azureAdInstance = new PublicClientApplication({
  auth: {
    clientId: 'your-client-id',
    authority: 'your-authority',
    redirectUri: 'your-redirect-uri'
  }
})

// 添加MSAL请求拦截器
azureAdInstance.addInterceptor({
  async beforeRequest(request) {
    const accounts = azureAdInstance.getAllAccounts()
    if (!accounts.length) {
      eventBus.emit('auth:require-login', {});
      return;
    }

    try {
      const tokenResult = await azureAdInstance.acquireTokenSilent({
        scopes: ["scope1"],
        account: accounts[0]
      })
      request.headers.set('Authorization', `Bearer ${tokenResult.accessToken}`)
    } catch (error) {
      if (error instanceof InteractionRequiredAuthError) {
        eventBus.emit('auth:require-interaction', { tokenRequest: { scopes: ["scope1"], account: accounts[0] } });
      } else {
        throw error
      }
    }
  }
})

后续交互逻辑处理同方案1,在MsalProvider内的组件中监听事件完成认证并重试请求。

内容的提问来源于stack exchange,提问作者Tom el Safadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:24:58