如何让React Router v6 Loader在Auth0完成后再发送请求?
解决React Router Loader与Auth0认证时序冲突问题
问题核心
页面重载时,React Router v6.4的loader会提前于Auth0认证完成执行,导致API请求未携带Bearer token,触发后端权限错误。现有路由结构中,RouterProvider在Auth0 Provider外层,loader执行时Auth0还处于初始化状态,token尚未获取。
解决方案步骤
1. 调整路由初始化时机:Auth0完成加载后再创建路由
将RouterProvider的初始化逻辑移到Auth0认证状态加载完成之后,确保loader执行前Auth0已完成token获取。
修改App.tsx,把路由创建逻辑放到Auth0加载完成的回调中:
import { useState, useEffect } from 'react'; import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'; import { RouterProvider, createBrowserRouter, Router } from 'react-router-dom'; import { ProtectedRoute } from './ProtectedRoute'; import { RawMediasPage } from './RawMediasPage'; import { AuthPage } from './AuthPage'; import { PATHS } from './paths'; import { Loader } from './components/Loader'; import { httpService } from './services/http'; // 定义路由配置模板 const getAppRoutes = () => [ { element: <ProtectedRoute />, children: [ { element: <InitApp />, children: [ { element: <BaseLayout />, children: [ { path: PATHS.rawMedia, element: <RawMediasPage />, loader: rawMediasLoader, }, // 其他带loader的路由 ], }, ], }, ], }, { path: PATHS.auth, element: <AuthPage />, }, ]; const AppContent = () => { const { isLoading, isAuthenticated, getAccessTokenSilently } = useAuth0(); const [router, setRouter] = useState<Router | null>(null); useEffect(() => { // 等待Auth0加载完成后初始化路由和token if (!isLoading) { const initApp = async () => { // 已认证用户先获取token,初始化axios拦截器 if (isAuthenticated) { try { const token = await getAccessTokenSilently(); httpService(() => Promise.resolve(token)); } catch (err) { console.error('Token初始化失败:', err); } } // 创建并设置路由 setRouter(createBrowserRouter(getAppRoutes())); }; initApp(); } }, [isLoading, isAuthenticated, getAccessTokenSilently]); // 加载中显示全局加载器 if (isLoading || !router) { return <Loader areaLoader />; } return <RouterProvider router={router} />; }; // 根组件包裹Auth0Provider export const RootApp = () => { const domain = process.env.REACT_APP_AUTH0_DOMAIN; const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID; const audience = process.env.REACT_APP_AUTH0_AUDIENCE; if (!(domain && clientId)) { return null; } const onRedirectCallback = (appState: any) => { window.location.href = appState?.returnTo || window.location.pathname; }; return ( <Auth0Provider domain={domain} clientId={clientId} redirectUri={window.location.origin} onRedirectCallback={onRedirectCallback} audience={audience} > <AppContent /> </Auth0Provider> ); };
2. 简化ProtectedRoute,移除重复的token获取逻辑
原来的ProtectedRoute中通过useEffect获取token的逻辑可以移除,因为已经在AppContent中完成了初始化:
import { useLocation, Navigate, Outlet } from 'react-router-dom'; import { useAuth0 } from '@auth0/auth0-react'; import { Loader } from './components/Loader'; const ProtectedRoute = () => { const location = useLocation(); const { isAuthenticated, isLoading } = useAuth0(); if (isLoading) { return <Loader areaLoader />; } return isAuthenticated ? <Outlet /> : <Navigate to={PATHS.auth} state={{ from: location }} replace />; }; export default ProtectedRoute;
3. 优化Loader:主动获取Token(可选但更可靠)
为了彻底避免依赖拦截器的异步初始化,直接在loader中获取token并携带到请求头:
import { getAuth0 } from '@auth0/auth0-react'; import { LoaderFunction } from 'react-router-dom'; export const rawMediasLoader: LoaderFunction = async () => { // 获取Auth0实例,直接调用getAccessTokenSilently const auth0 = getAuth0(); const token = await auth0.getAccessTokenSilently(); const response = await fetch('/api/raw-medias', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { // 抛出错误触发React Router的错误边界 throw new Response('获取原始媒体失败', { status: response.status }); } return response.json(); };
原理说明
通过将RouterProvider的初始化延迟到Auth0的isLoading状态结束后,确保所有loader执行时,Auth0已完成认证状态的加载,token要么已获取(已认证用户),要么路由会被重定向到登录页(未认证用户)。同时,loader主动获取token的方式,彻底避免了依赖axios拦截器的异步时序问题。
内容的提问来源于stack exchange,提问作者qweezz
相关产品推荐
相关产品推荐

