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

首次登录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;

问题原因

  1. 时序错误:loginUser的thunk中,调用dispatch(loadUser(res.data))时,localStorage.setItem('token', action.payload.token)还没执行(这行代码在loginUser.fulfilled的reducer里)。此时loadUser读取的localStorage.token是首次登录前的空值,导致请求头没有带上正确的新token,接口返回未授权。
  2. 参数传递错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:35:01