Learn 2FA Authentication - OWASP & Security Best Practices
Episode 14 of 23

Learn 2FA Authentication - OWASP & Security Best Practices

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.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

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.

Server-Side-Only Verification

Don't Trust the Client

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.

Avoid Status Enumeration

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.

Timing-Safe Comparison

Time-Based Attacks

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.

Handled by the Library

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:

JSSafe vs manual verification
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.

The Prohibition on Logging Secrets and Codes

Safe Logs

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:

Example of safe logs for MFA audit
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=3

Log 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.

Don't Log Request Bodies

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.

The Anti-Pattern: Writing TOTP Yourself

A Tested Library Is Safer

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:

Proven TOTP libraries
npm install otplib@13.4.1
pip install pyotp==2.10.0

otplib@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.

Cross-Ecosystem Compatibility

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.

The 2FA Security Checklist

A summary of the practices covered in episodes 7 through 14, in one list you can pin to the wall:

  • TOTP secret stored encrypted with AES-256-GCM.
  • Code verification only server-side with a timing-safe library.
  • Recovery codes hashed and single-use.
  • Rate limit of 5 attempts per minute on every code endpoint.
  • Replay protection with lastUsedStep.
  • HTTPS mandatory, cookies Secure HttpOnly SameSite.
  • CSP restricts the enrollment page's sources.
  • Log only events, never secrets or codes.
  • Don't write TOTP from scratch.

This list is the intersection of all previous episodes. If one item is missing, assume your 2FA feature isn't production-ready yet.

Conclusion

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:

  • Verify codes only on the server, never on the client.
  • Use generic error messages to prevent account enumeration.
  • Leave timing-safe comparison to the library.
  • Never log secrets, codes, or provisioning URIs.
  • Avoid request-body logging on auth routes.
  • Use otplib or pyotp, tested against the RFC test vectors.

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.

Learn 2FA Authentication - OWASP & Security Best Practices | Learn 2FA Authentication