Most comparisons of these two options come down to convenience against security, with a vague claim that keys are "more secure". There is a more precise answer, and it is written down in a standard.
The sentence that settles the phishing question
NIST SP 800-63B, section 5.2.5, is direct about it. Authenticators that involve the manual entry of an authenticator output, such as out-of-band and OTP authenticators, shall not be considered verifier impersonation-resistant, because the manual entry does not bind the authenticator output to the specific session being authenticated.
The same section explains the attack rather than leaving it implicit: in a machine-in-the-middle attack, an impostor verifier could replay the OTP authenticator output to the real verifier and successfully authenticate.
That is the whole mechanism. Your six-digit code is correct. It is simply correct everywhere, including on a page that only looks like your bank.
Why binding is the actual difference
The standard defines verifier impersonation resistance as establishing an authenticated protected channel with the verifier, then strongly and irreversibly binding a channel identifier to the authenticator output, for example by signing the two values together with a private key.
Read that against what each method physically does.
An authenticator app generates a number from a shared secret and a clock. It never learns which site you are on. You are the transport layer, and you can be misdirected.
A security key signs a challenge together with the identity of the session. The FIDO Alliance describes passkeys as using standard public key cryptography techniques to provide phishing-resistant authentication. There is no number for you to read out, and therefore nothing for an attacker to relay.

What the attack looks like from your side
The abstraction hides how ordinary this is, so it is worth walking through.
You receive a message about your account. The link goes to a page that renders correctly, because the attacker's server is fetching the real page and passing it through. You enter your password. The attacker's server submits it to the real site immediately. The real site asks for a second factor, so the fake page asks you for it too.
You open your app and read out six digits. The attacker's server enters them on the real site within the same thirty-second window. The real site sees a valid password and a valid code, and issues a session. The attacker now holds that session.
Notice what never happened. Nothing was cracked, no secret was stolen from your phone, and your app behaved exactly as designed. The code was genuine. It was simply used somewhere you did not intend, and neither you nor the app had any way to notice, because the code carries no information about its destination.
Repeat the same sequence with a security key and it stops at the second factor. The key does not produce a number for you to relay. It signs a response tied to the site actually asking, so the attacker's server receives something that only works on its own domain, which is worthless to it.
Where a key does not help either
Being precise about the limits matters as much as the strengths, because a key is not a general-purpose shield.
It does not protect a session already issued. If malware on your machine steals a session cookie after you have logged in legitimately, the login step is behind you and the key is no longer involved.
It does not protect the recovery path. An account that can be reset by email or by a support agent is only as strong as that path. This is precisely why the email account is the first one to protect, and why account recovery questions remain a soft target.
It does not protect what a compromised device sees. If the endpoint is fully compromised, the attacker is inside the authenticated session with you.
It does not cover services that do not support it. This is the practical limit for most people, and the reason a key is an addition to an authenticator app rather than a replacement for it.
The nuance most comparisons drop
Here is where a lot of security writing overshoots, so it is worth stating plainly.
Verifier impersonation resistance is required only at AAL3 in NIST's framework. At AAL1 and AAL2 it is not mandated. A TOTP app is therefore a conforming second factor for a very large share of ordinary accounts, and it remains a major improvement over SMS codes or over no second factor at all.
So the honest framing is not "apps are broken". It is that apps have one specific weakness, real-time phishing, and that this weakness is structural rather than a flaw in any particular app. No update to your authenticator app will fix it, because the gap is in the manual entry step itself.
Choosing without overthinking it
The decision follows from what an attacker gets if a given account falls.
Use a security key where a compromise is unrecoverable. Your email account first, because it is the password reset path for everything else. Then your password manager. Then anything financial that supports keys.
Keep the app for breadth. Most services still do not support security keys. An authenticator app on those accounts is far better than nothing, and it costs nothing.
Do not run a single key with no fallback. Register a second one, or keep recovery codes printed and stored offline. Losing your only key is a self-inflicted lockout, and it is a more common failure than being phished.
If you are deciding which key to buy, we compare the current models in our hardware security key comparison.
Side by side
| Authenticator app | Security key | |
|---|---|---|
| Resists real-time phishing | No, per NIST 5.2.5 | Yes, response bound to the session |
| What you handle | A code you read and retype | Nothing, the device signs |
| Required at AAL3 | Not sufficient alone | Meets the requirement |
| Works on most services | Yes | Only where supported |
| Cost | Free | Roughly 25 to 60 currency units per key |
| Main failure mode | You are phished into entering a valid code | You lose the key with no backup registered |
| Loss recovery | Reinstall from a backup of the seeds | Register a second key, or recovery codes |
The last two rows are the ones people underestimate. The dominant real-world risk with an app is being tricked into using it correctly at the wrong moment. The dominant real-world risk with a key is losing it after registering only one.
★ Audit Cure53 2024 · ✓ Plan gratuit · Cross-platform
Store the recovery codes somewhere you will still have themRecovery codes are what gets you back in when a key is lost. A password manager keeps them off the device you are trying to recover, which is the point.→The short version
The phishing question is not a matter of opinion. NIST states that OTP authenticators cannot be considered verifier-impersonation-resistant, and explains why: manual entry does not bind the code to the session. A security key signs the session itself, so there is nothing to relay.
But the requirement applies at AAL3, not everywhere. An authenticator app remains a legitimate second factor for most accounts. Put keys where a loss would be unrecoverable, keep the app for the long tail, and make sure you have a way back in.
The definitions and requirements described here are taken from NIST Special Publication 800-63B, and the characterisation of passkeys from the FIDO Alliance's own material, both checked at the time of writing. Standards are revised; verify against the current text before relying on a specific clause. Commercial links carry the rel="sponsored nofollow" attribute; an affiliate commission may apply at no extra cost to you.
★ Audit Cure53 2024 · ✓ Plan gratuit · Cross-platform
A manager with built-in 2FA & passkeys → NordPassStore TOTP & passkeys · XChaCha20 · free tier→Frequently asked questions
Is a security key really more phishing-resistant than an authenticator app?
On this specific point the standard is unambiguous. NIST SP 800-63B states that authenticators involving the manual entry of an output, such as out-of-band and OTP authenticators, shall not be considered verifier-impersonation-resistant, because the manual entry does not bind the output to the specific session being authenticated. A cryptographic authenticator signs a channel identifier together with its response, which is what a security key does and what a six-digit code cannot do.
Why does typing the code matter so much?
Because the code has no idea where you are typing it. NIST describes the attack directly: an impostor verifier can replay the OTP output to the real verifier and authenticate successfully. A fake login page collects your code and uses it within its validity window. The code is valid, the site is not, and nothing in the process detects the difference.
Does that mean authenticator apps are unsafe?
No, and it is worth being precise. Verifier impersonation resistance is required only at AAL3 in NIST's framework. At AAL1 and AAL2 it is not mandated, so a TOTP app remains a conforming second factor for a large share of ordinary accounts. It is a very large improvement over SMS and over no second factor at all. The gap is specific: it concerns real-time phishing, not the general strength of the method.
What makes a security key resistant where a code is not?
The credential is cryptographic rather than a transcribed number. The FIDO Alliance describes passkeys as using standard public key cryptography techniques to provide phishing-resistant authentication. The response is produced by the device and tied to the session, so there is nothing for you to read out and nothing for an attacker to relay.
Should I use both?
That is usually the practical answer. Use a security key on the accounts that would be worst to lose, typically your email and your password manager, since email is the reset path for everything else. Keep an authenticator app for the many services that do not support keys. Register a second key or keep recovery codes offline, because a single key with no fallback is its own kind of risk.



