A login handler keeps the pre-login session id — find and fix the flaw.
Below are the login and logout handlers of an Express web application.
Constraints:
- the password check is already correct and must not be changed
- the session store supports
regenerate()anddestroy() - the application is served over HTTPS only
app.post('/login', async (req, res) => {
const user = await findUser(req.body.email);
if (!user || !(await verify(req.body.password, user.hash))) {
return res.status(401).json({ error: 'invalid credentials' });
}
req.session.userId = user.id;
res.cookie('sid', req.sessionID);
res.json({ ok: true });
});
app.post('/logout', (req, res) => {
req.session.userId = null;
res.json({ ok: true });
});
Find and fix the session-management vulnerability.
The handler reuses the identifier the visitor arrived with, so whoever planted it owns the authenticated session — classic fixation; logout only blanks a field. Regenerate on login and privilege change, destroy on logout, harden the cookie.
- ✗Assuming the session store rotates the identifier on its own at login
- ✗Treating a blanked user field on logout as a destroyed session
- ✗Emitting the session cookie without
HttpOnly,SecureandSameSite
- →Which other event besides login must also trigger session regeneration?
- →Why must logout remove the server-side record and not only the cookie?
The vulnerability is session fixation. The application keeps the identifier the visitor arrived with, so an attacker who planted a known sid on the victim beforehand owns a fully authenticated session once she logs in. The second defect is logout: it blanks userId but leaves the server-side session record alive, so a previously stolen identifier keeps working.
The fix is to regenerate the session right after the password check, destroy it on logout, and set protective cookie attributes.
app.post('/login', async (req, res) => {
const user = await findUser(req.body.email);
if (!user || !(await verify(req.body.password, user.hash))) {
return res.status(401).json({ error: 'invalid credentials' });
}
req.session.regenerate((err) => {
if (err) return res.status(500).end();
req.session.userId = user.id;
req.session.createdAt = Date.now();
res.cookie('sid', req.sessionID, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
});
res.json({ ok: true });
});
});
app.post('/logout', (req, res) => {
req.session.destroy(() => {
res.clearCookie('sid');
res.json({ ok: true });
});
});
The same regenerate() call belongs on any privilege change — role elevation, password change, passing an extra factor — otherwise the old identifier inherits the new rights.