Engineer's toolkit
MFA and passkeys
How TOTP codes work, why SMS is the weakest second factor, and how passkeys resist phishing by binding each login to a single website.
Multi-factor authentication (MFA) asks for a second proof beyond the password. The three kinds of proof are something you know, something you have, and something you are. A stolen password is then not enough to log in. Phishing, reused passwords and credential stuffing all depend on the password being sufficient, so MFA blocks them.
Not every second factor is equal. What matters is whether an attacker can relay it to a fake site.
Walking through it: TOTP codes
TOTP (time-based one-time password, RFC 6238) is the six-digit code in authenticator apps. The server and the app share a secret once, at enrollment. Both compute:
- The current time step:
counter = floor(unix_time / 30). HMAC-SHA1(secret, counter), which is an HMAC from HMAC and webhook signing.- A short "dynamic truncation" of that 20-byte result, then take it modulo 10^6.
The sketch below computes the code for the RFC's test secret at time 59. The output matches the RFC's published six-digit value.
import hmac, hashlib, struct
def totp(secret: bytes, t: int, step: int = 30, digits: int = 6) -> str:
counter = struct.pack(">Q", t // step)
h = hmac.new(secret, counter, hashlib.sha1).digest()
off = h[-1] & 0x0F
code = (struct.unpack(">I", h[off:off + 4])[0] & 0x7FFFFFFF) % 10 ** digits
return str(code).zfill(digits)
print(totp(b"12345678901234567890", 59))Output:
287082A TOTP code is valid for about 30 seconds, and it is still phishable. A fake site can ask the user for the code and immediately replay it on the real site before it expires. Servers should also reject a code that was already used in its window. Otherwise an attacker who sees a code can reuse it.
SMS codes are better than nothing, but weak. An attacker can take over the phone number through a SIM swap, by talking the carrier into moving the number to a new SIM. SMS also travels over the carrier network, where interception is possible. Prefer app-based or hardware-backed factors.
Passkeys and WebAuthn
Passkeys use WebAuthn, a browser standard. At registration, the device creates a key pair. The private key stays on the device, in a secure enclave, a hardware key or a password manager that syncs passkeys between devices. The server stores only the public key.
At login, the server sends a random challenge. The device signs it, and the browser includes the origin (the exact scheme, host and port) in the signed data. The browser decides which passkey to offer, based on the domain of the page. A phishing site on a different domain gets nothing to sign for your site.
That origin binding is why passkeys resist phishing. A user can't be tricked into approving a login on the wrong site, because the passkey for example.com won't work on examp1e.com.
Note
Losing the only device that holds a passkey can lock a user out. Plan recovery before launch.
Account recovery is the attack surface
Attackers often skip the second factor by resetting the account instead, through a support call or a "lost my phone" flow. Require a fresh factor or a waiting period before disabling MFA.
Common mistakes
- Treating SMS as strong protection against targeted attackers.
- Allowing a TOTP code to be reused within its window.
- Making account recovery easier than login, so the second factor is bypassed.
- Forgetting a recovery path, so a user who loses their only device is locked out for good.
Check yourself
- Why can a phishing site relay a TOTP code but not a passkey response? Answer: a TOTP code isn't bound to a site, so the attacker can replay it. A passkey signature covers the origin, so it fails on any other domain.
- Why does SMS fail against a SIM swap? Answer: whoever controls the phone number receives the code, and the attacker can gain control of the number.
Key takeaways
- MFA stops a stolen password from being enough, but not all second factors resist phishing.
- TOTP is a shared-secret HMAC. It is better than SMS, but still phishable.
- Passkeys bind each login to one origin, which makes them phishing-resistant.
- Harden account recovery, since attackers often go around the second factor.
Further learning
The WebAuthn Guide at webauthn.guide explains the registration and login flow with interactive examples. Start there before reading the W3C spec.
webauthn.guideAn introduction to Web Authentication (WebAuthn), the new API that can replace passwords with strong authentication.Guide to Web Authentication
Next, return to the authentication and authorization overview.
Related
Authentication and authorization
Authentication proves who you are, authorization decides what you can do, and service-to-service calls need their own identity. The map for the rest of this topic.
HMAC and webhook signing
A keyed hash proves a message came from a key holder and was not changed. It protects webhooks, signed URLs and TOTP codes.
Password hashing
Passwords need slow, salted, memory-hard hashes. How the guessing math works, which algorithm to pick, and how to tune the cost for your login servers.