Passkeys Are Not 2FA
A while back I wrote a post titled OTP in Your Password Manager Is Not 2FA about how password managers offering to store OTP secrets and generate codes is an anti-pattern.
This post is along similar lines, about something I’ve noticed when setting up a sign-in security key for my accounts on the web.
Let me give a concrete demonstration of the issue using Cloudflare.
The first thing that happens in the security key onboarding flow is that the Firefox extension for Proton Pass, which I use as my password manager, offers to set up a passkey. Bear in mind that I already have a passkey in this same password manager for signing in.

If I click the X to ignore that, then I get a native macOS dialog. It also offers to set up a passkey and store it in Keychain.

To set up my YubiKey, I have to click More Options.

It should be pretty obvious that using a passkey, which lives in the same password manager as your main sign-in password/passkey is not two factors. Setting it up like this would be pointless.
Passkeys shouldn’t be the default option when the site initiates the flow to onboard a security key.
I investigated a little bit and found that websites can set the following options when calling navigator.credentials.create to hint to the browser that only a physical security key should be onboarded.
const cred = await navigator.credentials.create({
publicKey: {
challenge,
rp,
user,
pubKeyCredParams,
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
residentKey: "discouraged",
},
hints: ["security-key"],
timeout: 60000,
attestation: "none",
},
});
With this, Proton Pass still suggests setting up a passkey, but that’s probably an issue with the extension.
It might seem like a petty complaint, but this stuff matters for security, especially for high-value targets who don’t necessarily have an understanding of how to protect themselves.