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

useSWR重验证与React MSAL令牌刷新问题求助

问题根源与解决方案

1. 修复useToken的依赖与逻辑缺陷

你的useToken中useEffect依赖了token,这会导致无限循环(setToken触发useEffect,再次调用setToken),同时当令牌过期时,MSAL实例的账户状态变化无法触发重新获取令牌。修改如下:

// useToken.tsx
export const useToken = () => {
  const { instance, accounts } = useMsal();
  const [token, setToken] = useState<string | null>(null);
  const activeAccount = accounts[0];

  useEffect(() => {
    const fetchAccessToken = async () => {
      if (!activeAccount) return;
      try {
        // 注意:应该获取accessToken而非idToken,idToken用于身份标识,accessToken用于API请求
        const silentAuthResponse = await instance.acquireTokenSilent({
          ...loginRequest,
          account: activeAccount
        });
        setToken(silentAuthResponse.accessToken);
      } catch (silentAuthError) {
        if (silentAuthError instanceof InteractionRequiredAuthError) {
          // 这里不需要手动调用acquireTokenRedirect,MsalAuthenticationTemplate会处理交互
          // 手动调用会和模板的逻辑冲突
          instance.acquireTokenRedirect({
            ...loginRequest,
            account: activeAccount
          });
        } else {
          console.error("静默获取令牌失败:", silentAuthError);
          setToken(null);
        }
      }
    };

    fetchAccessToken();
  }, [instance, activeAccount]); // 依赖改为activeAccount,账户变化时重新获取令牌

  return token;
};

关键修正:

  • 移除token作为依赖,改为监听activeAccount变化
  • 优先使用accessToken而非idToken调用API(idToken不应该用于API授权)
  • 避免和MsalAuthenticationTemplate的交互逻辑重复

2. 让SWR在401时自动触发令牌刷新并重试

利用SWR的mutate机制,在捕获401错误时触发令牌刷新,然后重试请求:

// fetcher.tsx
import { mutate } from 'swr';

export async function fetcher(params: string[]) {
  const [url, token] = params;
  const requestOptions = getRequestOptions(token);
  const res = await fetch(url, requestOptions);

  if (res.status === 401) {
    // 清除当前请求缓存,触发useToken重新获取令牌后重试
    mutate(key => key?.includes(getCurrentUserUrl()), undefined, { revalidate: true });
    throw new Error("令牌过期,正在重新认证");
  }

  const body = await res.json();
  if (!res.ok) {
    throw new Error(`${res.status}: ${JSON.stringify(body.response)}`);
  }

  return body;
}

同时在useCurrentUser中调整缓存键,确保令牌变化时SWR能识别并重新请求:

// useCurrentUser.tsx
export function useCurrentUser(): SWRResponse<UserType,Error> {
  const token = useToken();
  // 缓存键包含token,令牌更新时自动触发重新请求
  return useSWRImmutable(token ? [getCurrentUserUrl(), token] : null, fetcher, {
    revalidateOnFocus: true, // 保留默认行为,令牌更新后会自动重试
    errorRetryCount: 1, // 只重试一次,避免无限循环
  });
}

3. 整合MsalAuthenticationTemplate的认证逻辑

你的ProtectedRoute已经使用了MsalAuthenticationTemplate,它会自动处理静默登录失败→重定向的流程,不需要在useToken中手动调用acquireTokenRedirect,否则会导致逻辑冲突。

修改RequireEnabled组件,移除冗余的状态同步:

// RequireEnabled.tsx
export const RequireEnabled: React.FC<RequireEnabledProps> = ({ children }) => {
  const { data: currentUser, isLoading, error } = useCurrentUser();

  if (error) return <ErrorDisplay error={new Error("获取用户信息失败")} />;
  if (isLoading || !currentUser) return <LoadingSpinner />;

  return currentUser.enabled ? <>{children}</> : <Navigate to='/not-enabled' replace />;
};

4. 修正MSAL实例的初始化逻辑

确保MSAL实例正确监听账户变化,避免activeAccount丢失:

// main.tsx
msalInstance.initialize().then(() => {
  const accounts = msalInstance.getAllAccounts();
  if (accounts.length > 0) {
    msalInstance.setActiveAccount(accounts[0]);
  }
  // 添加账户变化监听
  msalInstance.addEventCallback((event: EventMessage) => {
    if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
      const payload = event.payload as AuthenticationResult;
      msalInstance.setActiveAccount(payload.account);
    }
    // 监听账户移除事件
    if (event.eventType === EventType.ACCOUNT_REMOVED) {
      msalInstance.setActiveAccount(null);
    }
  });
  // ...其他代码
});

关键注意事项

  • 不要混用idToken和accessToken:idToken用于验证用户身份,accessToken用于API授权,后者有更短的过期时间,且MSAL会自动管理其刷新
  • 避免重复认证逻辑:MsalAuthenticationTemplate已经封装了完整的认证流程,自定义hook只需要专注于获取令牌
  • SWR缓存键必须包含令牌:确保令牌更新时SWR能触发重新请求,而不是复用旧缓存

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:50:08