This episode summarizes authentication security best practices from OWASP: server-side-only verification, timing-safe comparison, the prohibition on logging secrets and codes, and why to use a proven library like otplib instead of writing TOTP from scratch.

All the 2FA components are already in place: enrollment, verification, recovery, rate limiting, and transport. Episode 14 is the final layer that ties them together — security best practices from the OWASP Authentication Cheat Sheet that govern how this feature behaves as a whole.
The three rules discussed here often separate a secure 2FA feature from one that merely looks secure: server-side verification, timing-attack-resistant comparison, and logging discipline. Plus one classic anti-pattern: writing TOTP from scratch.
Every TOTP code must be verified on the server, never in browser JavaScript. Client-side validation is only for user convenience — for example ensuring the input is 6 digits — but the correct/wrong decision is absolute on the server. The client can be modified, and the data sent can be forged.
This practice also means the verification endpoint must reject nonsensical input before computing: length that isn't 6 digits, non-numeric, or missing fields. The less work the server does on invalid input, the smaller the attack surface.
Error messages that distinguish "wrong password" from "wrong code" help an attacker map out accounts. One generic response for credential failures — without saying which part failed — makes information gathering harder. Episode 6 applied this principle to code verification messages.
A normal string comparison returns false as soon as it hits a differing character. An attacker who can measure response time could guess the code character by character — an attack called a timing attack. A 6-digit code becomes much weaker if characters can be guessed one at a time.
The good news: libraries like otplib handle this comparison internally with a constant-time operation. You don't need to write the comparison yourself — just make sure you don't replace authenticator.check with a manual comparison:
const aman = authenticator.check(req.body.token, secret);
const rapuh = req.body.token === expectedCode;The aman line is computed with a timing-safe comparison by otplib, while the rapuh line leaks information through timing. The golden rule: let the library handle cryptographic comparisons.
Timing attacks on real web apps are harder to execute because network latency masks small differences, but never give the chance. The same defense principle applies to error messages and code paths: all outcomes must be processed through nearly identical paths.
A log line containing a Base32 secret, a TOTP code, or a provisioning URI is an accident waiting to happen. Temporary debuggers on verification endpoints often leave traces like this. Audit all log paths and make sure only events are logged:
mfa_enabled user=550e8400-e29b-41d4-a716-446655440000
mfa_disabled user=550e8400-e29b-41d4-a716-446655440000
mfa_failed user=550e8400-e29b-41d4-a716-446655440000 attempts=3Log the user identity and the outcome, not the input. If you need to keep a trace for forensics, store a one-way hash of the failed code — not the plaintext.
Middleware that logs the entire request body automatically captures passwords and TOTP codes. Configure logging to exclude the body on auth and MFA routes, or use a logging library that's aware of sensitive fields.
TOTP looks simple — HMAC, truncation, modulo — but the details are full of traps: counter endianness, Base32 encoding, padding, and window behavior. A self-written implementation that looks correct can produce codes that are sometimes accepted, sometimes not, or are incompatible with Google Authenticator.
Use a library that follows the RFC and has been tested against the test vectors:
npm install otplib@13.4.1
pip install pyotp==2.10.0otplib@13.4.1 for Node.js and pyotp for Python both follow RFC 4226 and RFC 6238, are tested against the official test vectors, and are compatible with Google Authenticator. Rewriting from scratch is only justified for learning purposes — episode 16 even uses a library to test the implementation.
Besides security, libraries offer one advantage a manual implementation can hardly match: compatibility. otplib and pyotp are tested against Google Authenticator, Microsoft Authenticator, and Authy across many versions, so a secret created in one ecosystem stays valid in another. A self-written implementation often passes internal tests but fails when used across apps.
A summary of the practices covered in episodes 7 through 14, in one list you can pin to the wall:
This list is the intersection of all previous episodes. If one item is missing, assume your 2FA feature isn't production-ready yet.
Episode 14 brought together OWASP best practices: purely server-side verification, timing-safe comparison handled by the library, logging discipline without secrets and codes, and the decision to use a proven library instead of writing TOTP from scratch.
The key takeaways:
In the next episode, episode 15, we will cover session, tokens, and trusted devices — short-lived sessions with id rotation, full logout, a separate trusted device cookie, and the relationship between JWT and the 2FA flag when a token is issued.