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

出现Error [ERR_HTTP_HEADERS_SENT]错误,求排查Passport登录逻辑问题

问题原因及修复方案

核心问题1:Passport验证回调中多次调用done()

你的Passport LocalStrategy验证回调存在逻辑漏洞:当用户存在且有密码时,调用异步的bcrypt.compare后,没有阻止后续代码执行。也就是说,bcrypt.compare还在异步处理时,代码已经走到了下方的return done(null, false, { message: "Please login with Google or Facebook" });,导致done()被调用两次——一次是bcrypt.compare回调内的调用,一次是后续return语句的调用。

Passport会在每次调用done()时尝试发送响应,两次调用直接触发「Cannot set headers after they are sent to the client」错误。

修复代码:

passport.use(
  new LocalStrategy(
    {
      usernameField: "signinEmail",
      passwordField: "signinPassword",
    },
    async (email, password, done) => {
      const user = await userService.getByEmailOrPhone(email);
      if (user) {
        if (user.password) {
          // 添加return阻止后续代码执行
          return bcrypt.compare(password, user.password, (err, res) => {
            if (!err) {
              if (res) {
                return done(null, user);
              }
              return done(null, false, { message: "Incorrect password" });
            }
            console.log(err);
            // 错误时调用done传递错误,避免请求挂起
            return done(err);
          });
        }
        return done(null, false, {
          message: "Please login with Google or Facebook",
        });
      }
      return done(null, false, { message: "Incorrect login" });
    }
  )
);

核心问题2:中间件顺序错误+参数逻辑错误

你的路由中,passport.authenticate("local")在validationMiddleware之前执行,意味着Passport会先尝试认证,再做参数验证,逻辑完全颠倒。正确顺序应该是先验证参数合法性,再执行认证。同时原代码的asyncHandler参数、用户信息取值路径都存在错误:

修复代码:

router.post(
  "/login",
  // 先做参数验证
  validationMiddleware({
    signinEmail: ["required", "email"],
    signinPassword: ["required", "min:6"],
  }),
  // 再执行Passport认证
  passport.authenticate("local"),
  // 修正asyncHandler的参数为标准(req, res, next)
  asyncHandler(async (req, res, next) => {
    // 修正用户信息取值路径为req.user
    const { accessToken, refreshToken } = await authService.authorizeById(
      req.user.user_id
    );
    if (accessToken) {
      return res.send({
        accessToken,
        refreshToken,
        success: true,
      });
    }
    throw new UnauthorizedException("");
  })
);

额外优化建议

把bcrypt.compare改成Promise风格调用,和整个回调的async/await风格统一,避免回调嵌套带来的逻辑混乱:

// 替换回调风格的bcrypt.compare
const isMatch = await bcrypt.compare(password, user.password);
if (isMatch) {
  return done(null, user);
}
return done(null, false, { message: "Incorrect password" });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:27:32