首次登录auth/login成功但auth/loadUser被拒绝问题求助
前端登录功能异常排查:loadUser请求首次登录被拒绝
Postman测试所有接口正常,确定问题出在前端。提交登录表单后,Redux DevTools显示auth/login/fulfilled(返回数据正确),但auth/loadUser/rejected,错误信息为“Not authorized to access this route”。首次登录时token已正确存入localStorage,但loadUser请求被拒绝;第二次登录时两个action均正常完成。
相关代码
LoginForm组件
import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux'; import { loginUser } from '../../slices/authSlice'; import '../../styles/components/Auth/LoginForm.css'; import { BrowserRouter , Navigate } from 'react-router-dom'; const LoginForm = () => { const dispatch = useDispatch(); const [formData, setFormData] = useState({ email: "", password: "" }); const {email, password } = formData; const onChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = (e) => { e.preventDefault(); dispatch(loginUser({email, password})); }; return ( <form action="" className="login__form" onSubmit={(e) => onSubmit(e)}> <div className="field-container"> <label>Email</label> <input type="email" name="email" value={email} onChange={(e) => onChange(e)} /> </div> <div className="field-container"> <label>Password</label> <input type="password" name="password" id="" value={password} onChange={(e) => onChange(e)} /> </div> <button type="submit">Log In</button> </form> ); }; export default LoginForm;
authSlice代码
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from 'axios'; import setAuthToken from "../utils/setAuthToken"; const initialState = { token: localStorage.getItem('token'), isAuthenticated: false, isLoading: true, user: null } export const loadUser = createAsyncThunk( 'auth/loadUser', async (_,{ rejectWithValue }) => { if(localStorage.token){ setAuthToken(localStorage.token); } try { const res = await axios.get('http://localhost:5000/api/auth/me'); return res.data; } catch (error) { return rejectWithValue(error.response.data); } } ) export const registerUser = createAsyncThunk( 'auth/registerUser', async (newUser, { dispatch, rejectWithValue }) => { try { const config = { headers: { 'Content-Type': 'application/json', }, }; const body = JSON.stringify(newUser); const response = await axios.post( 'http://localhost:5000/api/auth/register', body, config ); dispatch(loadUser(newUser)); return response.data; } catch (error) { return rejectWithValue(error.response.data); } } ); export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { dispatch, rejectWithValue }) => { const body = { email, password }; const config = { headers: { 'Content-Type': 'application/json', }, }; try { const res = await axios.post('http://localhost:5000/api/auth/login', body, config); dispatch(loadUser(res.data)); console.log(body) return res.data; } catch (err) { return rejectWithValue(err.response.data); } }); export const authSlice = createSlice({ name: 'auth', initialState, reducers:{ logout:(state, action) => { state.token = null; localStorage.removeItem('token'); state.isAuthenticated = false; state.user = null; } }, extraReducers: (builder) => { builder .addCase(loadUser.pending, (state) => { state.isLoading =true; }) .addCase(loadUser.fulfilled, (state, action) => { state.isLoading = false; state.isAuthenticated = true; state.user = action.payload; }) .addCase(loadUser.rejected, (state, action) => { state.isLoading = false; state.isAuthenticated = false; console.log(action.payload); }) .addCase(registerUser.pending, (state) => { state.isLoading = true; }) .addCase(registerUser.fulfilled, (state, action) => { state.token = localStorage.setItem('token', action.payload.token); state.isLoading = false; state.isAuthenticated = true; state.user = action.payload; }) .addCase(registerUser.rejected, (state, action) => { state.isLoading = false; /* state.isAuthenticated = false; */ console.log(action.payload); }) .addCase(loginUser.pending, (state) => { state.isLoading = true; }) .addCase(loginUser.fulfilled, (state, action) => { state.isAuthenticated = true; state.isLoading = false; state.user = action.payload; state.token = localStorage.setItem('token', action.payload.token); }) .addCase(loginUser.rejected, (state, action) => { state.isAuthenticated = false; state.isLoading = false; state.user = null; console.log(action.error); } ) } }); export const { logout } = authSlice.actions; export default authSlice.reducer;
setAuthToken工具函数
import axios from 'axios'; const setAuthToken = (token) => { if (token) { axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; } else { delete axios.defaults.headers.common['Authorization']; } console.log(axios.defaults.headers.common['Authorization']); }; export default setAuthToken;
问题原因
- 时序错误:
loginUser的thunk中,调用dispatch(loadUser(res.data))时,localStorage.setItem('token', action.payload.token)还没执行(这行代码在loginUser.fulfilled的reducer里)。此时loadUser读取的localStorage.token是首次登录前的空值,导致请求头没有带上正确的新token,接口返回未授权。 - 参数传递错误:
loadUser并不需要参数,它的逻辑是从localStorage读取token,但你在loginUser里传入了res.data,却没有在loadUser里使用这个参数,等于白传。
修复方案
方案1:调整loginUser的执行顺序,先存token再调用loadUser
修改loginUser的thunk逻辑,拿到登录返回的token后,先存入localStorage并设置请求头,再调用loadUser:
export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { dispatch, rejectWithValue }) => { const body = { email, password }; const config = { headers: { 'Content-Type': 'application/json', }, }; try { const res = await axios.post('http://localhost:5000/api/auth/login', body, config); // 先把token存入localStorage localStorage.setItem('token', res.data.token); // 手动设置请求头,确保loadUser请求能带上正确的token setAuthToken(res.data.token); // 调用loadUser,不需要传参数 dispatch(loadUser()); return res.data; } catch (err) { return rejectWithValue(err.response.data); } });
同时修正loginUser.fulfilled的reducer错误(localStorage.setItem返回undefined,不能赋值给state.token):
.addCase(loginUser.fulfilled, (state, action) => { state.isAuthenticated = true; state.isLoading = false; state.user = action.payload; state.token = action.payload.token; // 直接存token值,不是setItem的返回值 })
方案2:去掉多余的loadUser请求,直接用登录返回的用户数据
因为loginUser的返回结果已经包含了用户信息,完全不需要再调用loadUser接口,直接在reducer里设置状态即可:
// 修改loginUser thunk,去掉dispatch(loadUser) export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { rejectWithValue }) => { const body = { email, password }; const config = { headers: { 'Content-Type': 'application/json', }, }; try { const res = await axios.post('http://localhost:5000/api/auth/login', body, config); return res.data; } catch (err) { return rejectWithValue(err.response.data); } });
这样既避免了时序问题,又减少了一次不必要的接口请求。
内容的提问来源于stack exchange,提问作者tukitooo1998
相关产品推荐
相关产品推荐

