页面加载/刷新时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> ); };
Navbar组件(根据用户状态渲染)
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
相关产品推荐
相关产品推荐

