我的应用程序充当第三方服务的 OAuth 服务器,该服务器将通过我们的应用程序中的访问。身份验证过程全部发生在幕后,没有中间屏幕供用户单击登录按钮(因为该过程已经从我们的应用程序内启动)。挑战在于,由于所有 OAuth 请求都是从
iframe
内的第 3 方发起的(因此我们无法与它们交互或拦截它们),因此我们识别应进行身份验证的用户的唯一方法是通过会话饼干。
因此,在注册或登录我们的应用程序时,我们将
req.session.userId
设置为等于当前登录用户的 id。我们期望当 iframe
向我们的 OAuth 端点发起请求时,从 GET /oauth/code
开始,我们可以读取传入请求中 req.session.userId
的值。然而,无论我在 Express 会话上进行哪组配置,这似乎都不起作用。通过更详细地检查请求,我确实注意到具有 userId
值的会话存在于 req.sessionStore
中,因此我设置了 authCode 处理程序来循环这些会话并找到设置了 userId
的会话。
这似乎有效,直到我们在实际环境中注意到在某些特定情况下,用户会登录我们的应用程序并在其 iframe 中看到完全不同的用户帐户。 (一个示例是用户 A 在我们的应用程序和第 3 方中进行身份验证,然后用户 B 打开一个隐身窗口并直接导航到第 3 方网站,而无需登录我们的应用程序)。在这种情况下,用户 A 的会话似乎根据用户 B 的请求出现在 sessionStore 中。这显然是不行的,因此它似乎回到了绘图板并试图确定为什么我们没有得到
userId
根据传入请求从 req.session
返回值。
我将在这里发布相关代码,但如果还有其他内容可以帮助更好地理解问题,请告诉我。
/api/app.js
const session = require('express-session');
// Express Session Middleware
app.set('trust proxy', 1); // this is for ngrok in our local instnace
app.use(session({
secret: 'starboard penguin disco',
resave: true,
name: 'test',
proxy: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
sameSite: 'none',
secure: false, // this is false now for local testing but true in production
},
}));
/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));
// Save user id to sesson cookie
req.session.userId = id;
return res.status(201).json({
id,
email,
fullName,
jwt: accessToken,
refresh_token: refreshToken,
});
}
return next({
status: 400,
message: 'Invalid email or password',
});
} catch (err) {
return next({
status: 400,
message: 'Invalid email or password',
});
}
};
/api/handlers/oauth.js
// This handles GET calls to /oauth/code and is the first of 3 requests made by 3rd party
exports.getAuthCode = async (req, res) => {
try {
// Extract user ID from request
let userId = '';
console.log(req.session.userId) // returns 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('getting user id from session');
console.log(userId);
// Generate a secure random authorization code
const codeValue = crypto.randomBytes(16).toString('hex');
// Save the code along with the client ID, redirect URI, and user ID
const authCode = await AuthorizationCode.create({
code: codeValue,
clientId: req.query.client_id, // Assuming this is passed by the client
redirectUri: req.query.redirect_uri, // Assuming this is passed by the client
expiresAt: new Date(Date.now() + 10 * 60 * 1000), // Code expires in 10 minutes
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);
}
};
会话配置:您的会话配置似乎适合处理身份验证。但是,我们可以进行一些调整来增强安全性和可靠性:
app.use(session({
secret: 'starboard penguin disco',
resave: false, // Set to false to prevent unnecessary session updates
name: 'test',
proxy: true,
saveUninitialized: false, // Set to false to only save initialized sessions
cookie: {
httpOnly: true,
sameSite: 'none',
secure: true, // Set to true to enforce HTTPS-only cookies in production
},
}));
所做的更改:
resave:设置为 false 以防止不必要的会话更新。 saveUninitialized:设置为 false 仅保存已初始化的会话(即有数据)。这有助于减少存储开销。 secure:设置为 true 以在生产环境中强制执行仅 HTTPS 的 cookie。确保您的生产环境设置为 HTTPS。 会话存储:您在 req.sessionStore 中循环会话以查找用户 ID 的方法似乎效率低下,并且容易出现您所描述的问题。相反,您应该直接从 req.session 访问用户 ID:
exports.getAuthCode = async (req, res) => {
try {
const userId = req.session.userId; // Access user ID directly from session
if (!userId) {
throw new Error('User ID not found in session');
}
// Generate a secure random authorization code
const codeValue = crypto.randomBytes(16).toString('hex');
// Save the code along with the client ID, redirect URI, and user 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),
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);
}
};
这可确保您始终使用会话中存储的用户 ID 来生成授权代码。 检查一下我已经修复了一些东西并简要提及它