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

Express+React项目中req.user未定义且passport.deserializeUser未触发问题排查

问题描述

我查遍了所有类似问题,但没找到适配我情况的解决方案。我在做一个简单的Todo应用,想集成passport.js。当前技术栈:

  • 独立运行的Express应用(端口3001)和React应用(端口3000)
  • Typescript
  • express-session
  • passport-google-oauth20

谷歌策略登录流程看起来没问题:用户会被存入数据库(不存在的话),跳转首页也正常,但后续请求里打印req.user显示undefined,而且passport.deserializeUser函数里的日志根本没输出。


Passport配置

app.use(passport.initialize());
app.use(passport.session());

passport.use(
 new GoogleAuth.Strategy(
{
  clientID:"HIDDEN",
  clientSecret: "HIDDEN",
  callbackURL: "/auth/google/callback",
},
async (
  accessToken: string,
  refreshToken: string,
  profile: GoogleAuth.Profile,
  done: GoogleAuth.VerifyCallback
) => {
  let user = await getGoogleUser(profile.id);

  if (user) done(null, user);
  else {
    const userObj: any = {
      google: {
        id: profile.id,
        name: profile.displayName,
        email: profile._json.email,
      },
    };

    const newUser = await createGoogleUser(userObj);
    if (newUser) done(null, newUser);
    else done(new Error("error in google auth"));
  }
}));

认证路由

router.get("/google", passport.authenticate("google", { scope: ["profile", "email"] }));

router.get("/google/callback",
  passport.authenticate("google", {
    successRedirect: "http://localhost:3000",
  })
);

Serialize和Deserialize User

passport.serializeUser((user: any, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id: string, done) => {
  const user = await getUser(id);
  console.log("deserializeUser");
  done(null, user);
});

Session配置

app.use(
  session({
    secret: "testasdasdcode",
    saveUninitialized: false,
    resave: false,
    cookie: { maxAge: 1000 * 60 },
  })
);

前端登录按钮处理

window.location.href = "http://localhost:3001/auth/google";

(我知道这是不良实践,目前仅用于测试)


可能的问题及解决方案

1. Session中间件顺序错误

Express中间件按执行顺序生效,session必须在passport.initialize()和passport.session()之前加载。如果你的代码里session配置在passport初始化之后,直接调整顺序:

// 先加载session
app.use(
  session({
    secret: "testasdasdcode",
    saveUninitialized: false,
    resave: false,
    cookie: { maxAge: 1000 * 60 },
  })
);
// 再初始化passport
app.use(passport.initialize());
app.use(passport.session());

Passport依赖session中间件读取会话信息,顺序错误会导致无法识别会话,自然不会触发deserializeUser。

2. 跨域Cookie问题

前后端分属不同端口,属于跨域场景,浏览器默认不会在跨域请求中携带Cookie,导致后端无法识别登录状态。需要做两处调整:

后端配置

修改session的Cookie设置,同时配置CORS允许携带凭证:

// 安装cors依赖后配置
const cors = require('cors');
app.use(cors({
  origin: "http://localhost:3000", // 允许的前端域名
  credentials: true // 允许跨域携带Cookie
}));

// 调整session配置
app.use(
  session({
    secret: "testasdasdcode",
    saveUninitialized: false,
    resave: false,
    cookie: { 
      maxAge: 1000 * 60,
      sameSite: "none", // 跨域场景必须设置
      secure: process.env.NODE_ENV === "production", // 生产环境强制HTTPS,开发环境可设为false
      httpOnly: true // 建议开启,防范XSS攻击
    },
  })
);

前端配置

发送请求时明确携带凭证:

  • 用fetch:
fetch('http://localhost:3001/api/your-endpoint', {
  credentials: 'include'
});
  • 用axios:
axios.get('http://localhost:3001/api/your-endpoint', {
  withCredentials: true
});

3. Serialize/Deserialize逻辑异常

检查serializeUser存入的user.id是否有效,以及deserializeUser中的getUser(id)是否能正确查询到用户:

  • 在serializeUser中添加日志,确认存入的ID合法:
passport.serializeUser((user: any, done) => {
  console.log("序列化用户ID:", user.id);
  done(null, user.id);
});
  • 给deserializeUser添加异常捕获,避免异步错误导致done不执行:
passport.deserializeUser(async (id: string, done) => {
  try {
    const user = await getUser(id);
    console.log("反序列化找到用户:", user);
    if (!user) return done(new Error("用户不存在"));
    done(null, user);
  } catch (err) {
    done(err);
  }
});

4. 会话存储问题(次要)

默认express-session用内存存储,开发环境暂时可用,但如果服务器重启或会话量大会丢失数据。如果前面的问题都排查完仍有异常,可以换成持久化存储如connect-mongo,但当前问题大概率和这个无关。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:57:01