How the credProtect extension affects security key interoperability across Chrome, Safari and Firefox - and what relying parties can do.
Vincent
Created: March 2, 2026
Updated: March 2, 2026

You register a YubiKey on a website in Chrome. Everything works. You open Safari on the same machine, try to log in with the same key and it fails silently. No useful error message, no prompt for a PIN - just nothing.
Want to experiment with passkey flows? Try our Passkeys Debugger.
The root cause is credProtect - formally called credentialProtectionPolicy - a WebAuthn extension that Chrome automatically applies when creating discoverable credentials on security keys. Chrome does this with good intention: as the Chromium source explains, the goal is to ensure that "physical possession of a security key does not allow sign-in to a site that doesn't demand user verification." This is a sensible default that protects users from unauthorized access if their key is lost or stolen. The side effect, however, is that other browsers like Safari cannot always negotiate the higher requirements Chrome applied, causing authentication to fail silently.
This article explains what credProtect is, how Chrome, Safari and Firefox handle it differently and what relying parties should do to avoid locking out security key users across browsers.
This only affects you if you use userVerification: "preferred" instead of
"required". Many large-scale deployments deliberately choose "preferred" because
UV=required can cause hard failures in edge cases: platform authenticators where
biometric sensors are temporarily unavailable (e.g. macOS clamshell mode), sensor failures
at scale or environments where not all devices support
user verification. Using "preferred" reduces error
rates and avoids blocking users in production.
The trade-off: "preferred" is more forgiving for platform passkeys but triggers Chrome's
credProtect escalation logic for security keys, creating the cross-browser breakage
documented in this article.
If you always use UV=required, Chrome and every other browser will prompt for
PIN/biometric uniformly. In that case, credProtect Level 3 is harmless because user
verification is already enforced at the browser level and Chrome users a lower protection level.

Looking for a dev-focused passkey reference? Download our Passkeys Cheat Sheet. Trusted by dev teams at Ally, Stanford CS & more.
A real-world example: Google uses userVerification: "preferred" on accounts.google.com
for both passkey and
security key registration. The browser-specific behavior illustrates the problem well:
UV=preferred means Safari accepts the ceremony without user verification.
The credential is created but the UV flag is off.Google handles this gracefully on the server side: when UV is not delivered, Google still accepts the security key but marks it as "This key can only be used with a password" - effectively downgrading it to a second-factor-only credential rather than a standalone passkey. This means the user must still enter their password alongside the security key tap.
This pattern shows how large relying parties handle the preferred + no-UV case: accept
the credential but reduce its trust level. Smaller RPs that do not have this fallback
logic may simply break or silently create credentials that fail in other browsers.
The credProtect extension is defined in the CTAP 2.1 specification and allows the browser (or the relying party via the WebAuthn extensions input) to specify how the authenticator should protect a stored credential. It controls two things:
allowCredentials list without
user verificationThe extension defines three protection levels:
| Level | Policy name | Value | Discovery without UV | Use with allowCredentials without UV |
|---|---|---|---|---|
| 1 | userVerificationOptional | 0x01 | Allowed | Allowed |
| 2 | userVerificationOptionalWithCredentialIDList | 0x02 | Blocked | Allowed |
| 3 | userVerificationRequired | 0x03 | Blocked | Blocked |
Level 1 provides maximum backward compatibility. The authenticator responds to any request regardless of whether the user provided a PIN or biometric.
Level 2 introduces privacy protection for discoverable credentials. The
authenticator hides the credential during usernameless discovery flows (empty
allowCredentials) unless user verification is performed. It still responds when the
relying party provides the credential ID explicitly - supporting traditional
identifier-first authentication without a PIN.
Level 3 is the strictest mode. The authenticator refuses to use the credential for any purpose unless user verification succeeds. This effectively turns the security key into a hardware-enforced multi-factor device.
Chrome adds its own security defaults on top of the relying party's request. When creating discoverable credentials on security keys, the engine applies credProtect levels designed to protect users even when the RP does not explicitly request protection.
The full Chromium credProtect documentation describes this behavior:
Chromium will request a protection level of userVerificationOptionalWithCredentialIDList when creating a credential if residentKey is set to preferred or required. This ensures that simple physical possession of a security key does not allow the presence of a discoverable credential for a given RP ID to be queried.
Additionally, if residentKey is required and userVerification is preferred, the protection level will be increased to userVerificationRequired. This ensures that physical possession of a security key does not allow sign-in to a site that doesn't demand user verification.
If an explicit credProtect level is requested by the site, that will override these defaults.
This means a relying party that sends the following configuration:
const options = { publicKey: { authenticatorSelection: { residentKey: "required", userVerification: "preferred", }, // ... }, };
...will cause Chrome to silently set credProtect: 3 (userVerificationRequired) on
the CTAP MakeCredential command sent to the security key.
The resulting credProtect level in Chrome depends on the combination of residentKey
and userVerification:
residentKey | userVerification | Resulting credProtect level |
|---|---|---|
preferred | discouraged | Level 3 |
required | discouraged | Level 3 |
required | preferred | Level 3 |
required | required | Level 2 (UV enforced by client) |
The counterintuitive row is required + preferred resulting in Level 3 while
required + required only gets Level 2. The rationale: when the RP explicitly requires
UV, Chrome trusts the client-side enforcement and does not need the authenticator to
double-enforce it. But when the RP only "prefers" UV, Chrome hardens the credential at the
hardware level as a safety net.
The following is an excerpt from a real Chrome CTAP debug log during a registration on
webauthn.io with a YubiKey 5 series (FIDO_2_1_PRE firmware, AAGUID
2FC0579F-8113-47EA-B116-BB5A8DB9202A). The RP requested residentKey: "required" and
userVerification: "preferred":
FIDODebug <- 0x1 (kAuthenticatorMakeCredential) {1: h'C43B...', 2: {"id": "webauthn.io", "name": "webauthn.io"}, 3: {"id": h'...', "name": "corbadotest10"}, 4: [{"alg": -8}, {"alg": -7}, {"alg": -257}], 6: {"credProtect": 3}, 7: {"rk": true}, 9: 2}
Key observations:
6: {"credProtect": 3} - Chrome injected Level 3 even though the RP never requested it7: {"rk": true} - resident key (discoverable credential) is enabled9: 2 - PIN protocol v2 was used for the PIN/UV negotiation0xC5 confirms UP=1, UV=1, AT=1 - user was verified via
PINThe credential was created with Ed25519 (alg: -8) and stored with credProtect Level 3. From this point forward, this credential will refuse to respond to any assertion
request that does not include successful user verification.
Subscribe to our Passkeys Substack for the latest news.
Safari's WebAuthn implementation is built on Apple's AuthenticationServices framework, which is primarily designed for the iCloud Keychain platform authenticator. For platform credentials, Apple's Secure Enclave provides hardware-enforced protection equivalent to Level 3 by design - Touch ID, Face ID or the system passcode is always required.
However, when an external security key is used, Safari does not send the credProtect
extension in the CTAP MakeCredential command. Even if the relying party explicitly
requests credentialProtectionPolicy in the extensions input, Safari ignores it.
Safari also handles user verification differently for security keys with
UV=preferred:
UV=discouraged or UV=preferred: Safari does NOT prompt the user to create a PIN on a
security key that has no PIN set. The ceremony completes with User Presence only.UV=required: Safari prompts for PIN setup on the security key and will not proceed
without it.This means a credential created in Safari on a security key without a PIN:
Firefox historically lacked support for the credProtect extension. Testing shows the following extension support as reported by the browser:
| Browser | credProps | credentialProtectionPolicy | enforceCredentialProtectionPolicy |
|---|---|---|---|
| Safari | true | true | true |
| Firefox (pre-139) | true | false | false |
| Firefox (139+) | true | true | true |
| Chrome | true | true | true |
Firefox 139 (released mid-2025) added credProtect support through the authenticator-rs
Rust library. Unlike Chrome, Firefox adheres more strictly to the relying party's explicit
request rather than applying implicit escalation. If the RP does not request a
credentialProtectionPolicy, Firefox is more likely to leave the credential at Level 1 or
Level 2 depending on the authenticator defaults.
Firefox's implementation was partly motivated by the Infineon ECDSA side-channel vulnerability (YSA-2024-03) which demonstrated that credProtect Level 2 or 3 credentials are significantly more resistant to physical key-extraction attacks because the attacker cannot trigger signing operations without first obtaining the user's PIN.
The most common failure occurs when a security key registered in Chrome is subsequently used in Safari. Here is the step-by-step breakdown:
The relying party initiates a registration ceremony with residentKey: "required" and
userVerification: "preferred". Chrome escalates to credProtect Level 3 and prompts the
user for their security key PIN. The credential is created and stored on the YubiKey with
the internal policy flag set to userVerificationRequired.
The user opens Safari and attempts to log in. The relying party calls
navigator.credentials.get() with userVerification: "preferred" or even userVerification: "required" . Safari translates
this into a CTAP GetAssertion command. Because the security key already has a PIN set
(Chrome forced it during registration), Safari should in theory negotiate UV. However,
Safari's limited CTAP extension handling may fail to properly complete the PIN/UV
token handshake required by the credential's Level 3 policy. The user sees a generic error or "No credentials found" - with no indication of why.
From testing, the distinction is clear:
| Scenario | credProtect | Cross-browser result |
|---|---|---|
Chrome with UV=required | Level 2 | Works in Safari and Firefox |
Chrome with UV=preferred | Level 3 | Fails in Safari |
When UV=required is set, Chrome assigns Level 2 (trusting client enforcement) and the
credential works across browsers because Level 2 allows assertion with an explicit
credential ID without UV. When UV=preferred is set, Chrome assigns Level 3 and the
credential requires UV for every operation - breaking in Safari.
Igor Gjorgjioski
Head of Digital Channels & Platform Enablement, VicRoads
Corbado proved to be a trusted partner. Their hands-on, 24/7 support and on-site assistance enabled a seamless integration into VicRoads' complex systems, offering passkeys to 5 million users.
Passkeys that millions adopt, fast. Start with Corbado's Adoption Platform.
Start Free TrialThe credProtect extension is usable only during registration (create()) and is
processed directly by the authenticator - the user agent passes the input through to the
security key. Relying parties can explicitly control the credProtect level by including the
extension in the navigator.credentials.create() call:
const options = { publicKey: { authenticatorSelection: { residentKey: "required", userVerification: "preferred", }, extensions: { credentialProtectionPolicy: "userVerificationOptionalWithCredentialIDList", enforceCredentialProtectionPolicy: true, }, // ... }, };
| Extension input | Type | Description |
|---|---|---|
credentialProtectionPolicy | string | One of userVerificationOptional (experimental), userVerificationOptionalWithCredentialIDList or userVerificationRequired. Maps to CTAP values 0x01, 0x02 and 0x03 respectively |
enforceCredentialProtectionPolicy | boolean | If true, the create() call fails when the policy cannot be adhered to. If false, the system makes a best attempt to conform to the policy but still creates a credential if the exact level is not achievable |
userVerificationOptional (Level 1) while requesting a
discoverable credential may cause Chrome to override the request and still apply
Level 2 to protect user privacy.enforceCredentialProtectionPolicy: true with
userVerificationOptionalWithCredentialIDList on an authenticator that does not support
credProtect (older CTAP 2.0 firmware) will cause the registration to fail entirely.| Browser | Sends credentialProtectionPolicy to authenticator |
|---|---|
| Chrome | Yes |
| Firefox 139+ | Yes |
| Safari | No |
| Edge (Windows) | Yes |
There are two primary approaches depending on your deployment constraints:
userVerification: "required" for cross-platform flows#If your authentication flow might involve security keys (i.e. you do not filter with
authenticatorAttachment: "platform"), set userVerification: "required". This is the
simplest and most reliable approach. It avoids Chrome's escalation to Level 3, ensures
consistent PIN prompts across all browsers and produces Level 2 credentials that work
everywhere.
const options = { publicKey: { authenticatorSelection: { residentKey: "required", userVerification: "required", }, // ... }, };
UV=preferred and detect the UV gap server-side#If you cannot use UV=required because of error rate concerns in large-scale deployments,
you can keep userVerification: "preferred" and handle the consequences on the server.
This is what Google does on accounts.google.com. Google uses UV=preferred for both
passkey and security key registration. When a security key is registered in Safari without
a PIN (Safari does not prompt for PIN creation with UV=preferred), the UV flag comes back
as false. Google's server detects this and still accepts the credential but marks it as
"This key can only be used with a password" - downgrading it to a second-factor-only
credential that requires a password alongside it.
To implement this approach:
false,
store the credential with a reduced trust level.getClientExtensionResults() to check whether credProtect was applied and at which
level. If the extension was ignored (as in Safari), flag the credential accordingly.This gives you the error-rate benefits of UV=preferred for platform passkeys while
gracefully handling the security key edge case.
Subscribe to our Passkeys Substack for the latest news.
credProtect is a necessary security feature that prevents unauthorized credential enumeration on security keys. The problem is not the extension itself but the inconsistent way browsers handle it:
residentKey: "required" is combined with
userVerification: "preferred", creating credentials that require UV for every operationUV=preferredFor relying parties, the safest cross-browser approach is userVerification: "required"
for any flow that may involve security keys. If that is not feasible, identifier-first
flows and server-side UV detection provide workable alternatives.
As Safari evolves to support more WebAuthn Level 3 extensions and CTAP 2.1 features, the current interoperability friction will likely diminish. Until then, understanding Chrome's implicit credProtect escalation is essential for any deployment that supports FIDO2 hardware security keys across browsers.
Related Articles
Table of Contents