如何使React Router v6加载器仅在Auth0之后发送请求?

qwe*_*ezz 7 reactjs auth0 react-router-dom

我正在尝试利用 React Router 加载器。实际上,除了一种情况之外,它的效果非常棒。

我用于Auth0 React SDK授权。我传递getAccessTokenSilentlyaxios interceptor所有 API 调用,标头中都有不记名令牌。在某些路由上,我以这种方式使用 React Router v6.4 中的加载器:

路由器

              {
                path: PATHS.rawMedia,
                element: <RawMediasPage />,
                loader: rawMediasLoader,
              }
Run Code Online (Sandbox Code Playgroud)

在我重新加载页面之前它工作得非常好。页面重新加载加载器在 Auth0 之前发送请求。在这种情况下,加载程序发送没有承载令牌的请求,并且从后端返回错误。

如何解决这个问题?高技能的人暗示我如何解决这个问题,但不幸的是我没那么好。据我了解,我需要在路由中进行<RouterProvider router={router} />App.tsx深入的处理,以便 Auth0 首先针对令牌触发......但我不明白如何实现它。

AuthProvider.tsx

export const AuthProvider = (): JSX.Element | null => {
  const navigate = useNavigate();
  if (!(domain && clientId)) {
    return null;
  }

  const onRedirectCallback = (appState: any) => {
    navigate(appState?.returnTo || window.location.pathname);
  };

  return (
    <Auth0Provider
      domain={domain}
      clientId={clientId}
      redirectUri={window.location.origin}
      onRedirectCallback={onRedirectCallback}
      audience={audience}
    >
      <Outlet />
    </Auth0Provider>
  );
};
Run Code Online (Sandbox Code Playgroud)

受保护的路由.tsx

const ProtectedRoute = () => {
  const location = useLocation();
  const { isAuthenticated, isLoading, getAccessTokenSilently } = useAuth0();

  useEffect(() => {
    const getAccessToken = async () => {
      try {
        const token = await getAccessTokenSilently();
        return Promise.resolve(token);
      } catch (e) {
        console.log('getAccessToken error:', e);
      }
    };
    httpService(getAccessToken);
  }, [getAccessTokenSilently]);

  if (isLoading) {
    return <Loader areaLoader />;
  }

  return isAuthenticated ? <Outlet /> : <Navigate to="/auth" state={{ from: location }} replace />;
};
Run Code Online (Sandbox Code Playgroud)

路由器

export const router = createBrowserRouter([
  {
    element: <AuthProvider />,
    children: [
      {
        element: <ProtectedRoute />,
        children: [
          {
            element: <InitApp />,
            children: [
              {
                element: <BaseLayout />,
                children: [
                  // many children with loaders
                ],
              },
            ],
          },
        ],
      },
      {
        path: PATHS.auth,
        element: <AuthPage />,
      },
    ],
  },
]);
Run Code Online (Sandbox Code Playgroud)

Kal*_*ana 3

为了解决您的问题,您需要进行一些代码更改。我将一一介绍它们。请仔细查看文件扩展名。其中一些是,.ts另一些是.tsx

AuthProvider.tsx

export const AuthProvider = = ({ children }: { children: any }) => {
  const navigate = useNavigate();

  if (!(domain && clientId)) {
    return null;
  }

  const onRedirectCallback = (appState: any) => {
    navigate(appState?.returnTo || window.location.pathname);
  };

  return (
    <Auth0Provider
      domain={domain}
      clientId={clientId}
      redirectUri={window.location.origin}
      onRedirectCallback={onRedirectCallback}
      audience={audience}
    >
      {children}
    </Auth0Provider>
  );
};
Run Code Online (Sandbox Code Playgroud)

索引.tsx

import { BrowserRouter as Router } from 'react-router-dom';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <Router>
    <AuthProvider>
      <App />
    </AuthProvider>
  </Router>
);
Run Code Online (Sandbox Code Playgroud)

主路由.tsx

const MainRoutes = {
  path: '/',
  element: <MainLayout />,
  children: [
    {
      path: '/',
      element: <Home />
    },
    {
      path: '/users',
      element: <Users />
    },
    {
      path: '/users/:id',
      element: <AddEditUser />
    },
    {
      path: '/roles',
      element: <Roles />
    },
    {
      path: '/roles/:id',
      element: <AddEditRole />
    },
  ]
};

export default MainRoutes;
Run Code Online (Sandbox Code Playgroud)

索引.ts

import { useRoutes } from 'react-router-dom';
import { withAuthenticationRequired } from '@auth0/auth0-react';

const ThemeRoutes = () => {
  return useRoutes([MainRoutes]);
};

// This will protect all your routes at once.
export default withAuthenticationRequired(ThemeRoutes);
Run Code Online (Sandbox Code Playgroud)

应用程序.tsx

  • 使用本地存储可能不是一个好的选择。我这样做是为了演示目的。在您的情况下,使用拦截器来捕获令牌。
import { Provider } from 'react-redux';
import Routes from './routes';
import AppRoutes from './routes'; // This is mapped to index.ts

const App = () => {

  // You can use getAccessTokenSilently here instead of getIdTokenClaims
  const { user, getIdTokenClaims, isAuthenticated } = useAuth0();
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const getUserMetadata = async () => {
      setIsLoading(true);
      const tokenData = await getIdTokenClaims();
      if (tokenData) {
        // use a interceptor in here
        localStorage.setItem("token", tokenData['__raw']);
        setIsLoading(false);
      }
    };

    const storedAccessToken = localStorage.getItem("token");

    if (!storedAccessToken) {
      getUserMetadata();
    }
  }, [getIdTokenClaims, user]);

  if (isAuthenticated && isLoading) {
    return (
      <div>Loading...</div>
    );
  }

  return (
      <Provider store={store}>
        <Routes>
          <AppRoutes />
        </Routes>
      </Provider>
  );
};

export default App;
Run Code Online (Sandbox Code Playgroud)