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

React调用Express用户Profile接口时401 Unauthorized问题求助

React调用Express JWT认证接口返回401问题排查

我用React(运行于3000端口)和Express(运行于3001端口)开发应用,Express端采用JWT实现认证功能。注册、登录接口在Postman和React中均可正常工作,但React调用/users/profile获取用户数据时,控制台返回GET http://localhost:3001/users/profile 401 (Unauthorized)错误,无法渲染数据;而该接口在Postman中可正常返回JSON数据。

我确认React端已存储JWT cookie,但不清楚如何在React端完成认证流程。此前仅用Express结合hbs渲染页面,这是首次尝试React+JWT认证,已尝试调整React组件和Express路由,但仍未解决问题。以下是相关代码:


auth.js服务文件

const jwt = require('jsonwebtoken');
const models = require('../models');
const bcrypt = require('bcryptjs');

var authService = {
    signUser: function (user) {
        const token = jwt.sign({
            Username: user.Username,
            UserId: user.UserId
        },
            'secretkey',
            {
                expiresIn: '1h'
            }
        );
        return token;
    },
    verifyUser: function (token) {
        try {
            let decoded = jwt.verify(token, 'secretkey');
            return models.users.findByPk(decoded.UserId);
        } catch (err) {
            console.log(err);
            return null;
        }
    },
    hashPassword: function (plainTextPassword) {
        let salt = bcrypt.genSaltSync(10);
        let hash = bcrypt.hashSync(plainTextPassword, salt);
        return hash;
    },
    comparePasswords: function (plainTextPassword, hashedPassword) {
        return bcrypt.compareSync(plainTextPassword, hashedPassword);
    }
}

module.exports = authService;

注册路由

router.post('/signup', function (req, res, next) {
  models.users.findOrCreate({
    where: {
      Username: req.body.username
    },
    defaults: {
      FirstName: req.body.firstName,
      LastName: req.body.lastName,
      Email: req.body.email,
      Password: authService.hashPassword(req.body.password)
    }
  })
    .spread(function (result, created) {
      if (created) {
        res.redirect("http://localhost:3000/login");
      } else {
        res.send('This user already exist')
      }
    });
});

注册功能在Postman和React中均可正常使用。

登录路由

router.post('/login', function (req, res, next) {
  models.users.findOne({
    where: {
      Username: req.body.username
    }
  }).then(user => {
    if (!user) {
      console.log('User not found')
      return res.status(401).json({
        message: "Login Failed"
      });
    } else {
      let passwordMatch = authService.comparePasswords(req.body.password, user.Password);
      if (passwordMatch) {
        let token = authService.signUser(user);
        res.cookie('jwt', token);
        res.redirect('http://localhost:3001/users/profile');
      } else {
        console.log('Wrong Password');
      }
    }
  });
});

登录功能在Postman和React中均可正常使用。

Profile路由

router.get('/profile', function (req, res, next) {
  let token = req.cookies.jwt;
  if (token) {
    authService.verifyUser(token).then(user => {
      if (user) {
        res.setHeader('Content-Type', 'application/json');
        res.send(JSON.stringify(user));
      } else {
        res.status(401);
        res.send('Invalid authentication token');
      }
    });
  } else {
    res.status(401);
    res.send('Invalid authentication token');
  }
});

该接口仅在Postman中正常返回数据,React调用时返回401错误。

React侧Profile组件

import React from 'react';
import axios from 'axios';

class UserProfile extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            profileData: []
        }
    };

    fetchProfileData = () => {
        var encodedURI = window.encodeURI(this.props.uri);
        return axios.get(encodedURI).then(response => {
            this.setState(() => {
                return {
                    profileData: response.data
                };
            });
        });
    };

    componentDidMount() {
        this.fetchProfileData();
    }

    render() {
        console.log(this.state.profileData);
        if (this.state.profileData.length === 0) {
            return <div>Failed to fetch data from server</div>
        }

        const profile = this.state.profileData.map(user => (
            <div key={user.UserId}>Hello world</div>
        ));
        return <div>{profile}</div>
    }
}

export default UserProfile;

组件调用方式:

<UserProfile uri="http://localhost:3001/users/profile" />

调用后页面渲染“Failed to fetch data from server”,控制台输出401错误。

Express的app.js文件

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var models = require('./models');
var cors = require('cors');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');

app.use(function (req, res, next) {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

models.sequelize.sync().then(function () {
  console.log("DB Synced Up");
});

module.exports = app;

问题原因及解决办法

1. 跨域请求未携带Cookie

React和Express属于跨域场景,默认axios不会自动携带Cookie到跨域接口,同时Express端CORS配置未允许凭证传递。

  • React端修改:在axios请求中添加withCredentials: true,确保请求携带Cookie:
fetchProfileData = () => {
    var encodedURI = window.encodeURI(this.props.uri);
    return axios.get(encodedURI, { withCredentials: true }).then(response => {
        this.setState(() => {
            return {
                profileData: response.data
            };
        });
    });
};
  • Express端修改:删除手动设置的CORS Header,替换为带凭证允许的CORS配置:
// 删掉原手动设置Header的代码
// app.use(function (req, res, next) {
//   res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
//   res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
//   next();
// });

// 替换为配置好的cors中间件
app.use(cors({
  origin: 'http://localhost:3000',
  credentials: true
}));

2. 登录路由重定向逻辑错误

当前登录成功后重定向到Express的/profile接口,而非由React前端发起请求。建议返回JSON结果,由React前端控制页面跳转:

// 登录路由修改
if (passwordMatch) {
  let token = authService.signUser(user);
  res.cookie('jwt', token, { httpOnly: true, sameSite: 'lax' }); // 增加安全配置
  res.status(200).json({ message: 'Login success', redirectUrl: '/profile' });
}

React登录请求拿到响应后,通过路由跳转(如history.push)到自身的/profile页面,再发起数据请求。

3. React组件状态处理错误

接口返回单个用户对象,而非数组,但组件初始状态设为数组,导致length判断失效。修改状态初始化和渲染逻辑:

constructor(props) {
    super(props);
    this.state = {
        profileData: null // 初始化为null
    }
};

render() {
    console.log(this.state.profileData);
    if (!this.state.profileData) { // 判断是否获取到数据
        return <div>Loading...</div>
    }

    // 直接渲染单个用户数据
    return <div>Hello {this.state.profileData.Username}</div>
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:05:22