Multi-Factor Authentication Essentials
Multi-Factor Authentication Essentials MFA requires a user to prove their identity with two or more independent factors before granting access, instead of a pas…
Multi-Factor Authentication Essentials
MFA requires a user to prove their identity with two or more independent factors before granting access, instead of a password alone. The point isn't just "more steps" — it's that a single leaked or guessed credential (which happens constantly, via phishing, breach-reuse, or brute force) is no longer sufficient on its own. An attacker with your password but not your phone or security key is stopped cold. This is consistently the highest-leverage, lowest-cost security control an organization can roll out.
The Three Factor Types
Something you know — a password, PIN, or security question answer. This is the weakest factor on its own: it can be phished, guessed, reused across breached sites, or observed. It's the default first factor almost everywhere, which is exactly why a second, independent factor matters.
Something you have — a physical device: a phone running an authenticator app, a hardware security key (YubiKey), a smart card. This factor is defeated by theft of the physical device, but not by a remote attacker who only has your password.
Something you are — biometrics: fingerprint, face recognition. Convenient and hard to phish remotely, but biometrics can't be rotated if compromised (you can't issue yourself a new fingerprint), and implementations vary widely in how spoof-resistant they are.
True MFA combines factors from at least two different categories — a password plus a TOTP code (knowledge + possession) is MFA. Two passwords, or a password plus a security question, is not — it's still one category (knowledge), so it doesn't add real independence against the attacks MFA is meant to stop.
TOTP and HOTP
HOTP (HMAC-based One-Time Password, RFC 4226) generates a code from a shared secret and a counter that increments each time a code is used. TOTP (Time-based One-Time Password, RFC 6238) is the far more common variant: it replaces the counter with the current time, divided into fixed windows (typically 30 seconds), so both the server and the authenticator app derive the same code independently without needing to stay in sync over a network. This is what Google Authenticator, Authy, and most "scan this QR code" 2FA setups implement.
const crypto = require('crypto')
// Simplified TOTP generation (RFC 6238) — illustrates the mechanics;
// use a vetted library (otplib, speakeasy) in production.
function generateTOTP(secretBase32, timeStepSeconds = 30, digits = 6) {
const secret = base32Decode(secretBase32)
const counter = Math.floor(Date.now() / 1000 / timeStepSeconds)
const counterBuffer = Buffer.alloc(8)
counterBuffer.writeBigUInt64BE(BigInt(counter))
const hmac = crypto.createHmac('sha1', secret).update(counterBuffer).digest()
// Dynamic truncation: use the last nibble of the HMAC to pick an offset
const offset = hmac[hmac.length - 1] & 0x0f
const binCode =
((hmac[offset] & 0x7f) << 24) |
((hmac[offset + 1] & 0xff) << 16) |
((hmac[offset + 2] & 0xff) << 8) |
(hmac[offset + 3] & 0xff)
return String(binCode % 10 ** digits).padStart(digits, '0')
}
// Server-side verification should accept the current window AND
// one window before/after, to tolerate clock drift on the user's device
function verifyTOTP(submittedCode, secretBase32, timeStepSeconds = 30) {
const now = Math.floor(Date.now() / 1000 / timeStepSeconds)
for (const drift of [-1, 0, 1]) {
const expected = generateTOTPAtCounter(secretBase32, now + drift)
if (crypto.timingSafeEqual(Buffer.from(submittedCode), Buffer.from(expected))) {
return true
}
}
return false
}Two implementation details matter beyond the algorithm itself: allowing a small clock-drift window (usually ±1 step) so legitimate users with slightly out-of-sync clocks aren't locked out, and comparing codes with a timing-safe comparison to avoid leaking information through response-time side channels.
WebAuthn and Passkeys
WebAuthn is a W3C standard that lets a browser or OS talk directly to an authenticator (a hardware key, or a platform authenticator like Touch ID/Windows Hello) using public-key cryptography instead of a shared secret. On registration, the authenticator generates a key pair; the private key never leaves the device (often stored in a secure enclave/TPM), and the public key is sent to the server. On login, the server sends a random challenge, the authenticator signs it with the private key, and the server verifies the signature with the stored public key.
This design makes WebAuthn phishing-resistant in a way TOTP and SMS are not: the authenticator cryptographically binds the signature to the origin (domain) that requested it, so even if a user is tricked into visiting evil-login-page.com, the authenticator simply won't produce a valid signature for the real bank.com's challenge — there's no secret code for the user to accidentally type into the wrong site.
Passkeys are WebAuthn credentials designed to sync across a user's devices via their platform account (iCloud Keychain, Google Password Manager) and to fully replace passwords, not just supplement them — a passkey alone can be both factors in one interaction (possession of the device plus a biometric/PIN unlock counts as inherent MFA). This is the direction the industry is moving for consumer authentication, since it's simultaneously more secure and lower-friction than password-plus-OTP.
Why SMS OTP Is the Weakest Common Option
SMS-based one-time codes are better than no second factor, but they're the weakest widely-deployed MFA method, for reasons that are structural, not implementation bugs. SIM swapping lets an attacker port a victim's phone number to a SIM they control by social-engineering the carrier, after which all SMS codes go straight to the attacker. SS7 protocol weaknesses in the telecom signaling network allow interception of SMS messages in transit by a sufficiently resourced attacker. And SMS codes are phishable — unlike WebAuthn, nothing stops a user from reading a code off their phone and typing it into a fake login page. NIST's digital identity guidelines (SP 800-63B) have flagged SMS OTP as restricted for exactly these reasons, recommending authenticator apps or WebAuthn instead where feasible.
Adaptive / Risk-Based Authentication
Rather than prompting for a second factor on every single login, adaptive authentication evaluates contextual risk signals — new device, unfamiliar IP/geolocation, impossible-travel (login from two continents minutes apart), time-of-day anomaly — and only challenges for MFA when the risk score crosses a threshold. A login from a recognized device on a recognized network might skip the MFA prompt entirely; a login from a new country triggers it, or blocks the attempt outright pending verification.
This balances security against user friction: constant MFA prompts train users to tap "approve" reflexively (this is exactly how MFA fatigue/push-bombing attacks succeed — an attacker spams approval requests until an annoyed user taps yes), while adaptive auth reserves friction for the situations where it's actually informative. Number-matching (the user must enter a number shown on the login screen into the push notification, rather than tapping a bare "approve" button) is a specific mitigation against MFA fatigue that major identity providers have adopted.
Common Pitfalls
Treating SMS OTP as equivalent to an authenticator app or hardware key — it's meaningfully weaker due to SIM-swap and SS7 risks; offer it as a fallback, not the primary/only option, for anything sensitive.
No backup/recovery codes — if a user loses their only MFA device with no recovery path, they're locked out, which pushes teams toward risky manual support-desk bypass processes that attackers learn to social-engineer.
MFA fatigue / push bombing — bare "approve/deny" push notifications can be spammed until a user taps approve out of annoyance; number-matching and rate-limiting push requests mitigate this.
Storing TOTP secrets in plaintext — the shared secret is as sensitive as a password; it must be encrypted at rest, since anyone who reads it can generate valid codes indefinitely.
No clock-drift tolerance on TOTP verification — rejecting codes from a device whose clock is a few seconds off causes real, confusing login failures for legitimate users.
Allowing MFA to be disabled via a weak account-recovery flow (e.g. email-only reset) — this creates a bypass path that undermines the whole point of requiring a second factor in the first place.
Requiring MFA only at initial login, with sessions that never re-check it — a hijacked long-lived session token skips MFA entirely; sensitive actions (changing payment details, exporting data) often warrant step-up re-authentication even mid-session.