Two-Factor Authentication Compared: SMS vs Authenticator App vs Security Key
The gap between the weakest and strongest second factor is wider than the gap between no 2FA and SMS. What each method actually resists, why only one is phishing-proof, and the enrolment order that avoids locking yourself out.
TL;DR
Every second factor stops credential stuffing. Only one stops phishing. SMS codes fail to SIM swapping, time-based codes and push approvals can be relayed through a proxy in real time, and a FIDO2 security key or passkey cannot be, because its signature is cryptographically bound to the real site's origin. Enrol two hardware keys on anything that controls your other accounts, then remove SMS as a fallback β otherwise the weakest enrolled method defines your security.
Two-Factor AuthenticationAuthenticationAccount Security
Share:
Key Takeaways
- A second factor you can read aloud or type into a page can be relayed by a proxy in real time, which is why codes and push prompts are phishable and origin-bound signatures are not.
- NIST SP 800-63B classifies SMS and voice out-of-band authenticators as restricted, because carrier number porting and SS7 weaknesses put them outside the authenticator's control.
- TOTP is HMAC over a shared secret and a 30-second counter, which means the server holds a seed that can be stolen in a breach β unlike a FIDO2 credential, where the server only ever holds a public key.
- Push-approval fatigue attacks work by sending repeated prompts until someone taps Approve; number matching largely closes it by requiring a value shown on the login screen.
- Enrol two security keys, not one. A single key is a single point of lockout, and the recovery path is usually weaker than the key you are protecting.
- Leaving SMS enabled as a fallback after adding a stronger method means an attacker simply chooses the weaker path.
- All of this protects the login. The authenticated session that follows is a separate problem with separate controls.
"Turn on two-factor authentication" is sound advice that hides a large detail: the methods on offer differ by more than convenience. The distance between an SMS code and a hardware security key is larger, in terms of what attacks they stop, than the distance between no second factor at all and an SMS code.
The reason is one property, and it is worth naming up front. Anything you can read and retype can be relayed. A code on your screen is a secret you transport by hand into whatever page asked for it, and if that page belongs to an attacker proxying the real site, the code works exactly as designed β for them. A FIDO2 authenticator never shows you anything to retype. It signs a challenge bound to the origin that requested it, and a different origin gets a signature the real site refuses.
Everything below follows from that distinction.
What each method actually resists
Five attacks matter. Mapping methods against them is more useful than ranking them on a single axis.
| Credential stuffing | Real-time phishing | SIM swap | Server seed breach | Push fatigue | |
|---|---|---|---|---|---|
| Password only | β | β | n/a | β | n/a |
| Email OTP | β | β | β | β | n/a |
| SMS / voice OTP | β | β | β | β | n/a |
| TOTP app | β | β | β | β | n/a |
| Push approval (no number match) | β | β | β | β | β |
| Push with number matching | β | β | β | β | β |
| FIDO2 security key | β | β | β | β | β |
| Passkey | β | β | β | β | β |
One column separates the bottom two rows from everything above them. Real-time phishing β an adversary-in-the-middle proxy relaying a genuine login β passes every method that produces a human-transportable artifact, and fails against both methods that produce an origin-bound signature.
SMS and voice: the floor
A code by text message is the most widely deployed second factor and the weakest.
SIM swapping is the dominant attack and it is not technical. An attacker contacts the carrier with enough personal detail β often assembled from exactly the breach dumps described in how to check whether your credentials leaked β and requests a port or a replacement SIM. Your phone loses service, their device receives your messages. No malware, no exploit, one phone call to a support queue measured on handle time.
Signalling weaknesses add a second route. The SS7 protocol underpinning inter-carrier routing was designed when every participant was a trusted national operator, and interception and redirection via SS7 access have been demonstrated repeatedly.
NIST SP 800-63B reflects this directly: out-of-band authentication over the public telephone network is classified as a restricted authenticator, with the reasoning that the channel is outside the verifier's control and subject to number reassignment. "Restricted" is not "forbidden" β it carries an obligation to warn users and offer alternatives.
Keep it in perspective, though. SMS defeats credential stuffing completely, and credential stuffing is the attack that happens to ordinary people at scale. SMS is the floor, not a trap. The error is leaving it enabled alongside something stronger, because an attacker picks the path, not you.
Email codes: circular by construction
A code sent to your email protects your email account with your email account. For every other service it is a real second factor, provided the email account itself is protected by something stronger β and if it is not, every email-OTP account inherits that weakness.
Treat email OTP as a dependency rather than a method: it is exactly as strong as the inbox it arrives in.
TOTP: the practical default, and its sharp edge
Time-based one-time passwords are specified in RFC 6238, built on the HOTP construction in RFC 4226. The computation is small enough to state completely:
T = floor((unix_time - T0) / 30) # 30-second time step
hash = HMAC-SHA1(shared_secret, T)
code = dynamic_truncate(hash) mod 10^6 # six digits
Both sides hold the same shared_secret, derive the same counter from the clock, and compute the same code. Verifiers typically accept the adjacent time steps to tolerate clock skew, which is why a code that just expired sometimes still works.
When you scan an enrolment QR code, what it encodes is a URI:
otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30
That secret parameter is the whole credential. You can confirm it yourself with oathtool:
oathtool --totp -b "JBSWY3DPEHPK3PXP"
Which exposes the structural weakness: TOTP is a shared secret. The server stores a seed that, if breached, lets an attacker generate your codes indefinitely. Compare FIDO2, where the server holds only a public key and a breach yields nothing usable. A handful of real incidents have involved stolen TOTP seeds, and no amount of care on your side mitigates it.
The second weakness is the phishing one. A proxy page captures your six digits and submits them to the real site inside the validity window. The code is genuine. The session it creates belongs to someone else.
Still, TOTP is the right answer for most accounts most of the time: it works offline, it costs nothing, it is nearly universally supported, and it removes the SIM-swap and interception routes entirely.
Choosing an app comes down to export and backup:
| App | Platform | Encrypted export | Notes |
|---|---|---|---|
| Aegis Authenticator | Android | Yes, encrypted vault | Open source, local-first |
| 2FAS | iOS, Android | Yes | Open source, optional sync |
| Ente Auth | Cross-platform | Yes, end-to-end | Open source, synced |
| Google Authenticator | iOS, Android | Account sync | Historically no export at all |
| Authy | iOS, Android | Encrypted backup | Desktop app discontinued |
Avoid any app with no export path. A phone that dies without an export means re-enrolling every account by hand, which is how people end up relying on recovery codes they never saved.
Should TOTP live in your password manager? An honest trade-off rather than a rule. One vault holding both factors means a single compromise yields both, collapsing two factors into one. It also means your codes survive a lost phone and are available on every device. For ordinary accounts the convenience usually wins, because the realistic failure is losing the phone, not the vault being breached. For the accounts that control other accounts β primary email, the password manager itself, cloud storage, anything financial β keep the second factor on separate hardware.
Push approval, and the fatigue problem
A push prompt β "Approve this sign-in?" β fixes the shared-secret problem. The server stores no seed, there is nothing to type, and the channel is the vendor's own.
It introduced a human one. MFA fatigue, or push bombing, works by triggering prompts repeatedly, often at 3am, until the target taps Approve to make it stop. It was central to several widely reported breaches of large technology companies, and it required no technical capability beyond a valid password.
Number matching is the fix. The login screen displays a two- or three-digit number that you must enter in the app rather than just tapping Approve. A blind approval becomes impossible, because the attacker's screen is the one with the number and the victim's is not. Microsoft, Okta, and Duo all enable this by default now. If your push implementation only offers Approve and Deny, treat it as roughly equivalent to TOTP with worse offline behaviour.
Push is still phishable through a proxy: the attacker triggers the real login, the real prompt arrives with the real number, and a victim who believes they are signing in reads it off the attacker's page.
FIDO2 security keys: the one that breaks the pattern
A security key β YubiKey, Google Titan, SoloKey, Nitrokey β implements WebAuthn and CTAP2. Registration generates a key pair on the device, hands the public key to the site, and keeps the private key in hardware that has no API for exporting it.
The signing step is where the properties come from:
assertion = Sign(privateKey, challenge || rpIdHash || clientDataJSON)
rpIdHash is a hash of the relying party ID β the real site's domain β and clientDataJSON carries the origin the browser actually loaded. Both are inputs to the signature.
So when a proxy at accounts-google.secure-login.example asks for an assertion, one of two things happens: the authenticator has no credential registered for that relying party and produces nothing, or the browser supplies the attacker's origin and the real site rejects the resulting signature. There is no user decision in the loop to get wrong. This is the structural difference, and the detailed mechanics are in how WebAuthn and FIDO2 changed browser authentication.
Three practical points:
- Buy two. One on your keyring, one in a drawer or a safe, both enrolled on every important account. A single key is a single point of lockout, and the fallback path β account recovery through support β is usually weaker than the key you were protecting.
- Match the connector. USB-C with NFC covers a modern laptop and a phone. USB-A still exists on desktops. Check before ordering.
- Discoverable credentials matter. A key storing resident keys can identify the account itself, enabling a usernameless flow. Capacity for these is finite, typically a few dozen slots.
The friction is real: you carry a physical object and touch it when prompted. For an email account that can reset every other account you own, that is a reasonable price.
Passkeys: phishing resistance without the keyring
A passkey is a FIDO2 credential whose private key is held by the platform β Apple's Keychain, Google Password Manager, Windows Hello, or a password manager β and synced across your devices, unlocked by a biometric or device PIN.
It gets origin binding, so it is phishing-resistant for the same reason a hardware key is. It trades hardware isolation for recoverability: the credential now lives wherever your platform account syncs, so compromise of that account is the thing to worry about rather than loss of a physical object.
Terminology trips people up. A passkey usually replaces the password rather than supplementing it, so "two-factor" is the wrong frame β it is one step that combines possession of the device with a local biometric. Strong, just differently shaped. How passkeys are replacing passwords covers the model and its boundary.
Backup codes are credentials
Every service hands out single-use recovery codes at enrolment and most people screenshot them into their photo library, where they get synced to the cloud, indexed by on-device text recognition, and shared accidentally.
Treat them as what they are: a list of passwords that bypass your second factor.
- Store them in your password manager as a secure note, or print them and put them somewhere physical.
- Never in email, never in a photo library, never in a plaintext file called
codes.txt. - Regenerate them after any suspected compromise. Old codes stay valid until you do.
What to enrol, by account class
| Tier | Accounts | Primary | Backup |
|---|---|---|---|
| A | Primary email, password manager, cloud storage, bank, broker, domain registrar | Two hardware keys, or passkey + key | Printed recovery codes |
| B | Work SSO, GitHub, social, payment apps | TOTP in an app with encrypted export, or a passkey | Recovery codes in the manager |
| C | Shopping, forums, newsletters | Whatever is offered | β |
Tier A is where the leverage is. Your email resets everything; your registrar controls your domain's DNS and therefore your email. Two keys each, and then remove SMS from those accounts entirely.
That last step is the one people skip. Adding a security key while leaving SMS enabled as a fallback does not raise your floor β an attacker who can SIM-swap simply chooses that path. Enrol the strong method, verify it works from a second device, then delete the weak one. Order matters, because deleting first can lock you out.
For Google accounts specifically, the Advanced Protection Program enforces this properly: keys or passkeys only, no SMS fallback, restricted third-party data access, and tighter download scanning. It is free, and it is the right setting for anyone whose email is worth attacking.
Where to enrol
Google myaccount.google.com/security β 2-Step Verification
Microsoft account.microsoft.com/security β Advanced security options
Apple Settings β [name] β Sign-In & Security
GitHub Settings β Password and authentication
Facebook Settings β Password and security β Two-factor authentication
Amazon Login & security β 2-step verification
A five-step routine that avoids the usual mistakes:
- Enrol the strongest method the service supports.
- Enrol a second instance of it β second key, second device.
- Download and store recovery codes properly.
- Sign out everywhere and sign back in to confirm the new method works.
- Remove weaker methods, SMS first.
The layer none of this covers
Every method above answers one question: is the person logging in the account owner? Once answered, the service issues a session, and the session is a separate security problem with separate failure modes.
A stolen session cookie replays an authenticated session without touching the login flow, which is why hardened accounts still get taken over β covered in session hijacking. And an authenticated tab sitting open on an unlocked machine is readable by anyone who sits down, regardless of how it was authenticated. For that narrower case a tab-level unlock such as Locksy puts a password in front of specific tabs' contents while the rest of the browser stays usable.
Strong authentication and session protection are different layers. Getting the first one right is the higher-value work, and it is what this page is for β but finishing it does not finish the job.
The short version
Enable something on everything. On the handful of accounts that control the rest, enrol two hardware keys or passkeys, store recovery codes like credentials, and then delete SMS. For everything else, a TOTP app with an encrypted export is the right balance of effort and effect.
The single most common mistake is not choosing the wrong method. It is leaving the weak one enabled after adding a strong one.
Frequently Asked Questions
What is the most secure two-factor authentication method?
Is SMS two-factor authentication better than nothing?
Can an authenticator app be phished?
Should I use the same app for passwords and 2FA codes?
What happens if I lose my security key?
Are passkeys the same as two-factor authentication?
VS
Vansh SethiAuthor
Developer & Founder of Locksy
Vansh is the developer and founder of Locksy β a privacy-first browser tab security extension trusted by 5,000+ users across Chrome, Edge, Firefox, and Brave. He writes about browser security, privacy, and developer tools.
Related Articles
Technical
A session cookie is a bearer token β whoever holds it is you. How infostealers, reverse-proxy phishing kits, and XSS get one, why multi-factor authentication does not stop any of it, and what actually binds a session to a device.
Research
Passkeys are finally here, revolutionizing online security. But do they make you truly safe? Discover why even with passkeys, browser tab protection still mat
Tutorial
Four ways to check whether your credentials are in a breach β including the k-anonymity API call that never sends your password anywhere β plus the ordered triage that actually locks an attacker out.