Password Reset and MFALesson 6.4
How to enforce MFA during the login flow
two-step login, partial authentication state, MFA challenge step, session state machine, MFA bypass risks, remember device token
The Two-Step Login State Machine
MFA requires a two-phase login. After verifying the password, do not immediately authenticate the user. Set a partial state in the session and prompt for the TOTP code.
// Phase 1: password check
app.post('/auth/login', async (req, res) => {
const user = await verifyCredentials(req.body);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
if (user.mfaEnabled) {
req.session.pendingUserId = user.id;
return res.json({ mfaRequired: true });
}
req.session.regenerate((err) => {
req.session.userId = user.id;
res.json({ message: 'Logged in' });
});
});
// Phase 2: TOTP verification
app.post('/auth/mfa', async (req, res) => {
const userId = req.session.pendingUserId;
if (!userId) return res.status(401).json({ error: 'No pending login' });
const user = await db.findById(userId);
const valid = speakeasy.totp.verify({
secret: user.totpSecret,
encoding: 'base32',
token: req.body.code,
window: 1
});
if (!valid) return res.status(401).json({ error: 'Invalid code' });
delete req.session.pendingUserId;
req.session.regenerate((err) => {
req.session.userId = user.id;
res.json({ message: 'Logged in' });
});
});
