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
相关产品推荐
相关产品推荐

