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

Express会话存储的userId在后续请求中丢失的OAuth认证问题

问题背景与核心困扰

我把自己的应用做成了第三方服务的OAuth服务器,对方通过我应用内的iframe访问。认证全程在后台自动完成,不需要用户点登录按钮——因为流程是从我的应用里发起的。现在遇到的关键问题是:所有OAuth请求都是iframe里的第三方发起的,我没法交互或拦截这些请求,只能靠会话Cookie来识别要认证的用户。

用户注册或登录时,我会把req.session.userId设为当前登录用户的ID。本来预期iframe发起OAuth请求(比如GET /oauth/code)时,能直接从req.session里读到userId,但不管怎么配置Express的会话,这个值始终是undefined。我排查后发现,带userId的会话明明存在于req.sessionStore里,于是临时改了authCode的处理逻辑,遍历所有会话去捞带userId的项。

这个临时办法在测试环境凑合用,但到了生产环境出大问题了:比如用户A完成我的应用和第三方的认证后,用户B用无痕窗口直接打开第三方网站(根本没登录过我的应用),结果B的请求居然能读到A的会话,导致B看到的是A的账户。现在必须搞清楚为什么req.session.userId拿不到,相关代码如下:

/api/app.js

const session = require('express-session');

// Express Session 中间件配置
app.set('trust proxy', 1); // 本地用ngrok测试时的配置
app.use(session({
  secret: 'starboard penguin disco',
  resave: true,
  name: 'test',
  proxy: true,
  saveUninitialized: true,
  cookie: {
    httpOnly: true,
    sameSite: 'none', 
    secure: false, // 本地测试设为false,生产环境会改成true
  },
}));

/api/auth.js

exports.login = async (req, res, next) => {
  try {
    const user = await db.User.findOne({
      email: req.body.email,
    });
    const { id, fullName, email } = user;

    const isMatch = await user.comparePassword(req.body.password, next);

    if (isMatch) {
      ({ accessToken, refreshToken } = generateTokens(user));

      // 把用户ID存入会话Cookie
      req.session.userId = id;

      return res.status(201).json({
        id,
        email,
        fullName,
        jwt: accessToken,
        refresh_token: refreshToken,
      });
    }

    return next({
      status: 400,
      message: '邮箱或密码无效',
    });
  } catch (err) {
    return next({
      status: 400,
      message: '邮箱或密码无效',
    });
  }
};

/api/handlers/oauth.js

// 处理第三方发起的第一个请求:GET /oauth/code
exports.getAuthCode = async (req, res) => {
  try {
    // 从请求中提取用户ID
    let userId = '';
    console.log(req.session.userId) // 这里一直输出undefined
    const sessionStore = promisify(req.sessionStore.all.bind(req.sessionStore));
    const sessions = await sessionStore();
    Object.values(sessions).forEach((session) => {
      if (session.userId) {
        userId = session.userId;
      }
    });
    console.log('从会话仓库中获取用户ID');
    console.log(userId);

    // 生成安全随机的授权码
    const codeValue = crypto.randomBytes(16).toString('hex');
    // 保存授权码及关联的客户端ID、重定向URI、用户ID
    const authCode = await AuthorizationCode.create({
      code: codeValue,
      clientId: req.query.client_id, // 假设由客户端传入该参数
      redirectUri: req.query.redirect_uri, // 假设由客户端传入该参数
      expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 授权码10分钟后过期
      user: { id: userId },
    });

    await authCode.save();
    return res.redirect(`${req.query.redirect_uri}?code=${authCode.code}`);
  } catch (err) {
    console.error(err);
    return res.status(err.code || 500).json(err);
  }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 08:31:10