如何在 jwt 验证反应之前阻止路由注册

How to prevent a route from registering before jwt verifies react

今天我遇到了一个问题,在我的 API 验证用户的 JWT 令牌是否有效之前,反应加载了路由。使用 EJS 时,我可以将中间件传递给路由,中间件不会包含 next() 参数。结果,服务器不会呈现 EJS,而这正是我想要通过 React 实现的。也可以让 useNavigate 在导航特定路线时不重新加载吗?

我在 App.js 的路线目前是这样的:

<Route element={<ProtectedRoute access={access}></ProtectedRoute>}>
  <Route
    path="/login"
    exact
    element={<Login login={login} access={access}></Login>}
  ></Route>
  <Route
    path="/signup"
    exact
    element={<Signup signup={signup} access={access}></Signup>}
  ></Route>
  <Route
    path="/forgot-password"
    exact
    element={<ForgotPassword access={access}></ForgotPassword>}
  ></Route>
  <Route
    path="/reset-password"
    exact
    element={<ResetPassword access={access}></ResetPassword>}
  ></Route>
</Route>;

访问函数如下所示:

const access = async (token) => {
  return await axios.post(
    "http://localhost:5000/access",
    {},
    { headers: { Authorization: `Bearer ${token}` } }
  );
};

受保护的路由组件如下所示:

import { useState, useContext } from "react";
import { useLocation, useNavigate, Outlet } from "react-router-dom";
import AuthContext from "../Context/AuthProvider";

const ProtectedRoute = ({ access }) => {
  const [authorized, setAuthorized] = useState(false);
  const { auth } = useContext(AuthContext);
  const navigate = useNavigate();

  const authorize = async () => {
    try {
      await access(auth.accessToken);
      setAuthorized(true);
    } catch (err) {
      setAuthorized(false);
    }
  };

  authorize();

  if (authorized) {
    navigate('/');
  } else {
    return <Outlet></Outlet>;
  }
};

export default ProtectedRoute;

当我使用此代码时,我的登录组件在代码导航回主页之前呈现了一点,我如何使登录组件完全不呈现,只是让它留在主页上?

问题

  1. ProtectedRoute 组件的初始 authorized 状态掩盖了已确认的未验证状态,并且由于组件不等待身份验证确认,它会愉快地错误地重定向到 "/"
  2. ProtectedRoute 组件通过 navigate 函数错误地将导航操作作为无意 side-effect 发出,并且在未经身份验证的情况下 return 不是有效的 JSX。请改用 Navigate 组件。
  3. 如果用户获得授权,ProtectedRoute 应该呈现 Outlet 以呈现受保护的路由,并且仅在未经授权时重定向到登录。

解决方案

ProtectedRoute 组件应使用不确定的初始 authorized 状态,该状态与经过身份验证的 [=60 都不匹配=] 未验证状态,并且 等待 以在呈现 OutletNavigate 组件之前确认身份验证状态.

示例:

import { useState, useContext } from "react";
import { useLocation, Navigate, Outlet } from "react-router-dom";
import AuthContext from "../Context/AuthProvider";

const ProtectedRoute = ({ access }) => {
  const location = useLocation();
  const [authorized, setAuthorized] = useState(); // initially undefined!

  const { auth } = useContext(AuthContext);

  useEffect(() => {
    const authorize = async () => {
      try {
        await access(auth.accessToken);
        setAuthorized(true);
      } catch (err) {
        setAuthorized(false);
      }
    };

    authorize();
  }, []);

  if (authorized === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return authorized
    ? <Outlet />
    : <Navigate to="/login" replace state={{ from: location }} />;
};

将登录路由移到ProtectedRoute布局路由之外。

<Routes>
  <Route
    path="/login"
    element={<Login login={login} access={access} />}
  />
  <Route
    path="/signup"
    element={<Signup signup={signup} access={access} />}
  />
  <Route
    path="/forgot-password"
    element={<ForgotPassword access={access} />}
  />
  <Route
    path="/reset-password"
    element={<ResetPassword access={access} />}
  />
  ... other unprotected routes ...

  <Route element={<ProtectedRoute access={access} />}>
    ... other protected routes ...
  </Route>
</Routes>

保护login/signup/forgot/reset/etc条路线

创建一个 AnonymousRoute 组件,在身份验证状态上反转 OutletNavigate 组件。这次经过身份验证的用户被重定向到路由之外。

const AnonymousRoute = ({ access }) => {
  const [authorized, setAuthorized] = useState(); // initially undefined!

  const { auth } = useContext(AuthContext);

  useEffect(() => {
    const authorize = async () => {
      try {
        await access(auth.accessToken);
        setAuthorized(true);
      } catch (err) {
        setAuthorized(false);
      }
    };

    authorize();
  }, []);

  if (authorized === undefined) {
    return null; // or loading indicator/spinner/etc
  }

  return authorized
    ? <Navigate to="/" replace />
    : <Outlet />;
};

...

<Routes>
  <Route element={<AnonymousRoute access={access} />}>
    <Route path="/login" element={<Login login={login} access={access} />} />
    <Route path="/signup" element={<Signup signup={signup} access={access} />} />
    <Route path="/forgot-password" element={<ForgotPassword access={access} />} />
    <Route path="/reset-password" element={<ResetPassword access={access} />} />
    ... other protected anonymous routes ...
  </Route>
  
  ... unprotected routes ...

  <Route element={<ProtectedRoute access={access} />}>
    ... other protected authenticated routes ...
  </Route>
</Routes>