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

React useEffect在JWT认证后跳转安全路由时状态不更新问题

React Router安全路由重定向问题修复

问题场景

在React应用中使用React Router实现/secret安全路由,用户登录后通过JWT生成Cookie存储在浏览器。访问/secret时前端向后端发送令牌验证请求:

  • 后端验证通过返回200,前端允许访问该路由
  • 验证失败则重定向至/login路由

但实际运行时,即使后端验证通过,前端的状态更新无法生效,导致始终被重定向到/login页面,无法访问/secret。

代码示例

前端SecretPage组件

import { useState, useEffect } from "react";
import { Navigate } from "react-router-dom";

export default function SecretPage() {
    const [redirect, setRedirect] = useState(false);

    useEffect(() => {
        async function authChecker() {
            try {
                const response = await fetch("http://localhost:4000/authenticate", {
                    credentials: "include",
                });

                if (response.ok) {
                    setRedirect(true); // 此处状态更新未生效
                } else {
                    console.error('Authentication failed:', response.statusText);
                }
            } catch (error) {
                console.error('Error checking authentication:', error);
            }
        }

        authChecker();
    }, []);

    if (!redirect) {
        return <Navigate to="/login" state={{ message: "You are not logged in, please login" }} />;
    }

    return (
        <h1>SecretPage</h1>
    );
}

后端验证路由

app.get("/authenticate", (req, res) => {
    const token = req.cookies.jwt;
    if (token) {
        jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
            if (err) {
                console.error('Error verifying token:', err);
                return res.status(401).json({ message: 'Unauthorized' });
            } else {
                return res.status(200).json({ message: 'Access granted' });
            }
        });
    } else {
        res.status(401).json({ message: 'Unauthorized' });
    }
});

问题原因

  1. 初始渲染逻辑错误:组件初始渲染时redirect默认值为false,此时直接触发<Navigate>重定向,即使后续setRedirect(true)更新了状态,组件已经完成跳转,无法继续渲染SecretPage内容。
  2. 缺少加载状态处理:没有等待后端验证请求完成就执行重定向判断,请求处于pending状态时就直接跳转到登录页。

解决方案

方案一:修改SecretPage组件,添加加载状态

将状态变量改为isAuthenticated,用null表示加载中状态,等待验证请求完成后再决定页面跳转:

import { useState, useEffect } from "react";
import { Navigate } from "react-router-dom";

export default function SecretPage() {
    const [isAuthenticated, setIsAuthenticated] = useState(null); // null表示加载中

    useEffect(() => {
        async function authChecker() {
            try {
                const response = await fetch("http://localhost:4000/authenticate", {
                    credentials: "include",
                });

                setIsAuthenticated(response.ok);
                if (!response.ok) {
                    console.error('Authentication failed:', response.statusText);
                }
            } catch (error) {
                setIsAuthenticated(false);
                console.error('Error checking authentication:', error);
            }
        }

        authChecker();
    }, []);

    // 加载中时显示提示,避免直接重定向
    if (isAuthenticated === null) {
        return <div>验证身份中...</div>;
    }

    // 验证失败才触发重定向
    if (!isAuthenticated) {
        return <Navigate to="/login" state={{ message: "您未登录,请先登录" }} />;
    }

    return (
        <h1>SecretPage</h1>
    );
}

方案二:封装通用路由守卫组件(推荐,复用性强)

将身份验证逻辑抽离成独立的ProtectedRoute组件,所有需要保护的路由都可以复用该组件:

// ProtectedRoute.jsx
import { useState, useEffect } from "react";
import { Navigate, Outlet } from "react-router-dom";

export default function ProtectedRoute() {
    const [isAuthenticated, setIsAuthenticated] = useState(null);

    useEffect(() => {
        async function checkAuth() {
            try {
                const response = await fetch("http://localhost:4000/authenticate", {
                    credentials: "include",
                });
                setIsAuthenticated(response.ok);
            } catch (err) {
                setIsAuthenticated(false);
                console.error("Auth check failed:", err);
            }
        }
        checkAuth();
    }, []);

    if (isAuthenticated === null) {
        return <div>验证身份中...</div>;
    }

    return isAuthenticated ? <Outlet /> : <Navigate to="/login" state={{ message: "您未登录,请先登录" }} />;
}

在路由配置中使用该守卫:

// App.jsx
import { Routes, Route } from "react-router-dom";
import ProtectedRoute from "./ProtectedRoute";
import SecretPage from "./SecretPage";
import LoginPage from "./LoginPage";

function App() {
    return (
        <Routes>
            <Route path="/login" element={<LoginPage />} />
            <Route element={<ProtectedRoute />}>
                {/* 所有需要验证的路由都嵌套在此 */}
                <Route path="/secret" element={<SecretPage />} />
            </Route>
        </Routes>
    );
}

额外注意事项

确保后端配置了正确的CORS规则,允许前端携带Cookie:

// 后端CORS配置示例
const cors = require('cors');
app.use(cors({
    origin: "http://localhost:3000", // 替换为你的前端地址
    credentials: true // 允许携带cookie
}));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:10:20