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

页面加载/刷新时JWT令牌无法传递至后端API求助

页面刷新时JWT令牌无法传递到后端,热更新后恢复正常

问题描述

页面加载或刷新时,前端可从localStorage读取到JWT令牌,但调用后端/auth/login接口时,后端接收不到该令牌,返回401未授权状态,导致UserContext无法正确设置用户登录状态。但在Vite中保存文件触发热更新后,令牌能正常传递至后端,API验证通过并返回正确用户信息。

相关代码

React用户上下文组件

const UserContext = ({ children }: AccountContextProviderProps) => {
  const [user, setUser] = useState({user: "", loggedIn: false});
  const navigate = useNavigate();

  useEffect(() => {
    const fetchData = async () => {
      try {
        const token = localStorage.getItem('token');
        console.log(token);
        if (token) {
          axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
          console.log("token is here aswell");
        }

        const response: AxiosResponse<{ message: string, type: string, user: string, loggedIn: boolean }> = await API.get('/auth/login');
        console.log(response)
        if (response.data.type === 'success' && response.status < 400 && response.data.loggedIn) {
          setUser({ user: response.data.user, loggedIn: true });
          console.log(user, response.data);
          navigate("/");
        } else {
          throw new Error(response.data.message);
        }
      } catch (error: any) {
        console.error(error.response?.data?.message || 'An error occurred during signup.');
      }
    }

    fetchData();
  }, []); // Include navigate in the dependency array

  return (
    <AccountContext.Provider value={{ user, setUser }}>
      {children}
    </AccountContext.Provider>
  );
};
const Navbar = () => {
  const {user} = useContext(AccountContext);

  return (
    <div className='header'>
    <div className='container nav-container'>
        <div className='logo'>
          <h1>Logo</h1>
        </div>
        <nav>
  {user.user && user.loggedIn ? (
    <ul className='nav-list'>
      <li><a className='active-page' href="/">Home</a></li>
      <li><a href="/leaderBoard">LeaderBoard</a></li>
      <li><a href="/">Logout</a></li>
    </ul>
  ) : (
    <ul className='nav-list'>
      <li><a className='active-page' href="/">Home</a></li>
      <li><a href="/leaderBoard">LeaderBoard</a></li>
      <li><a href="/signup">Signup</a></li>
      <li><a href="/login">Login</a></li>
    </ul>
  )}
</nav>

    </div>
    </div>
  )
}

后端验证API(protect中间件)

const jwt = require("jsonwebtoken");
const asyncHandler = require("express-async-handler");
const {User} = require("../models/user.model");

const protect = asyncHandler(async (req, res, next) =>{
    let token;
    console.log(req.headers.authorization);
    if(req.headers.authorization && req.headers.authorization.startsWith("Bearer")){
        try{
            // Getting the token from header
            token = req.headers.authorization.split(" ")[1];

            // verify token
            const decoded = jwt.verify(token, process.env.JWT_SECRET);
            req.user = await User.findById(decoded.id).select("-password");
            res.status(200).json({message: "authorized", type:"success", loggedIn:true, user:req.user});
            next();
        }catch(error){
            res.status(401).json({message:"not authorized", type:"error", loggedIn:false});
            //throw new Error("Not authorized");
        }
    }
    if(!token){
        res.status(401).json({message:"not authorized", type:"error", loggedIn:false});
        //throw new Error("Not authorized, no token");
    }
})

已尝试操作

  • 通过console.log排查代码执行顺序,未定位到问题
  • 预期API能验证令牌并返回用户登录状态,实际仅在Vite热更新后正常

解决方案

1. 修正Axios请求头传递问题

你当前修改的是全局Axios的默认Header,但实际请求使用的是API.get(独立创建的Axios实例)。页面刷新时,API实例早已初始化,全局Header设置无法同步到已创建的实例上;而热更新时代码重新执行,API实例重新创建,会继承最新的全局设置,因此能正常传递令牌。

有两种可靠的解决方式:

方式一:直接修改API实例的默认Header

// 替换原有的axios.defaults...代码
if (token) {
  API.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}

方式二:请求时手动携带Header(推荐)

// 去掉全局Header设置,在请求时直接传入
const response = await API.get('/auth/login', {
  headers: {
    Authorization: token ? `Bearer ${token}` : undefined
  }
});

2. 修正后端中间件的响应逻辑

你的protect中间件存在逻辑错误:验证成功后先调用res.json()发送响应,再执行next(),这会导致后续路由尝试再次发送响应,引发错误。中间件的职责是验证权限,不应直接返回响应,需由后续路由处理函数返回用户信息。

修正后的中间件:

const protect = asyncHandler(async (req, res, next) => {
  let token;
  console.log(req.headers.authorization);
  
  if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
    try {
      token = req.headers.authorization.split(" ")[1];
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      req.user = await User.findById(decoded.id).select("-password");
      // 验证通过,交给后续路由处理响应
      next();
    } catch (error) {
      res.status(401).json({ message: "not authorized", type: "error", loggedIn: false });
    }
  } else {
    res.status(401).json({ message: "not authorized", type: "error", loggedIn: false });
  }
});

对应的路由处理示例:

// 根据你的实际路由结构调整
router.get('/auth/login', protect, (req, res) => {
  res.status(200).json({ 
    message: "authorized", 
    type: "success", 
    loggedIn: true, 
    user: req.user 
  });
});

3. 补充:修正useEffect依赖项

useEffect内部使用了navigate,虽然React Router的useNavigate是稳定引用,但为了符合React Hooks规则,建议将navigate加入依赖数组:

useEffect(() => {
  // ... 原有代码
}, [navigate]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 12:24:59