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

Node.js+React+MongoDB登录JWT更新异常:首次无Token问题排查

问题

我基于Node.js Express后端开发React网站的登录功能,需求是用户登录时生成JWT Token并更新至MongoDB数据库,前端验证后将Token存入localStorage。但注册后首次登录,返回结果中无Token导致无法登录,再次点击登录按钮才会生成并更新Token。明明在resolve中返回findOneAndUpdate的结果,为何首次响应只返回findOne查询到的用户数据?

相关代码如下:

Auth Controller

login(params) {
    params.email = params.email.toLowerCase();

    return new Promise((resolve, reject) => {
      db.collection("Users").findOne({ email: params.email }).then((response) => {

          console.log(response)
          if(response) {
            bcrypt.compare(params.password, response.password, (err, success) => {
              if(success) {
                let token = jwt.sign({
                  name: response.name,
                  id: response._id
                }, proccess.env.JWT_SECRET); // 注意拼写错误:proccess → process

                db.collection("Users").findOneAndUpdate({
                  email: params.email
                }, {
                  $set: { token: token, lastLogin: new Date() },
                }, function (e, s) {
                  if(e) {
                    console.log(e)
                    reject(e)
                  } else {
                    console.log("updated")
                    resolve(s)
                  }
                })
              } else {
                reject({msg: 'Incorrect email or password.'})
              }
            })
          } else {
            reject({msg: 'cannot log in user'});
          }
      })
    })
  }

Auth Router

router.post('/login', (req, res) => {
    let User = new models.User()
    let processes = [];
    processes.push(function (callback) {
        User.login(req.body).then(function (response) {
                callback(null, response);
        }, function (error) {
            console.log(error)
            callback(error);
        });
    });

    async.waterfall(processes, function (error, data) {
        if (!error) {
            return res.json({
                statusCode: 200,
                msg: 'User logged in successfully.',
                result: data
            });
        } else {
            return res.json({
                statusCode: 401,
                msg: 'Cannot login user.',
                error: error
            });
        }
    });
})

React Login.js

const login = () => {
    axios.post('/login', data).then(async (response) => {
      console.log(response)
      if(response && response.data.result.value.token ) {
        localStorage.setItem("authUser", JSON.stringify(response.data.result.value.token))
        history.push("/")
        console.log(response.data.result)
      } else {
        console.log("ERROR")
      }
    })
  }
解决方案

核心原因

MongoDB的findOneAndUpdate方法默认返回更新前的文档。首次登录时,数据库里的用户文档还没有token字段,所以返回的旧文档自然不带token;第二次登录时,数据库已经有了上次更新的token,返回的旧文档里包含该token,所以前端能正常获取。

修复步骤

  1. 修改findOneAndUpdate参数,返回更新后的文档
    根据你使用的MongoDB驱动版本,添加对应的参数:

    • 驱动4.0及以上版本:添加returnDocument: 'after'
    • 旧版本驱动:添加returnNewDocument: true

    修改后的findOneAndUpdate代码:

    db.collection("Users").findOneAndUpdate(
      { email: params.email },
      { $set: { token: token, lastLogin: new Date() } },
      { returnDocument: 'after' }, // 关键参数:返回更新后的文档
      function (e, s) {
        if(e) {
          console.log(e)
          reject(e)
        } else {
          console.log("updated")
          resolve(s)
        }
      }
    )
    
  2. 修正拼写错误
    代码中proccess.env.JWT_SECRET的proccess是拼写错误,应改为process.env.JWT_SECRET,否则会导致JWT签名失败。

  3. 优化代码(可选)
    把回调式的代码改成Promise/async-await风格,避免回调地狱,代码更易维护:

    async login(params) {
      params.email = params.email.toLowerCase();
      const user = await db.collection("Users").findOne({ email: params.email });
      if (!user) {
        throw { msg: 'cannot log in user' };
      }
      const isPasswordValid = await bcrypt.compare(params.password, user.password);
      if (!isPasswordValid) {
        throw { msg: 'Incorrect email or password.' };
      }
      const token = jwt.sign(
        { name: user.name, id: user._id },
        process.env.JWT_SECRET
      );
      const updatedUser = await db.collection("Users").findOneAndUpdate(
        { email: params.email },
        { $set: { token, lastLogin: new Date() } },
        { returnDocument: 'after' }
      );
      return updatedUser;
    }
    

前端验证调整(可选)

确保前端能正确识别更新后的文档结构,比如response.data.result.token(根据驱动返回的结构调整,有些版本返回的是response.data.result.value)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:50:37