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

