Get your free and exclusive +30-page Authentication Analytics Whitepaper

WebAuthn credProtect: Security Keys

How the credProtect extension affects security key interoperability across Chrome, Safari and Firefox - and what relying parties can do.

Vincent Delitz

Vincent

Created: March 2, 2026

Updated: March 2, 2026

Blog-Post-Header-Image

1. Introduction#

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.

Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

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.

2. When does this matter?#

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.

PasskeysCheatsheet Icon

Looking for a dev-focused passkey reference? Download our Passkeys Cheat Sheet. Trusted by dev teams at Ally, Stanford CS & more.

Get Cheat Sheet

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:

  • Chrome: when a security key has no PIN set, Chrome still forces PIN creation because of the credProtect escalation described below. User verification is delivered and the credential is fully functional as a passkey.
  • Safari: when the same security key has no PIN set, Safari does NOT prompt the user to create one. 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.

3. What is credProtect (credentialProtectionPolicy)?#

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:

  • Whether the credential can be enumerated (discovered) without user verification
  • Whether the credential can be used via an explicit allowCredentials list without user verification

The extension defines three protection levels:

LevelPolicy nameValueDiscovery without UVUse with allowCredentials without UV
1userVerificationOptional0x01AllowedAllowed
2userVerificationOptionalWithCredentialIDList0x02BlockedAllowed
3userVerificationRequired0x03BlockedBlocked

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.

4. How Chrome applies credProtect defaults#

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.

4.1 Chrome's implicit escalation matrix#

The resulting credProtect level in Chrome depends on the combination of residentKey and userVerification:

residentKeyuserVerificationResulting credProtect level
preferreddiscouragedLevel 3
requireddiscouragedLevel 3
requiredpreferredLevel 3
requiredrequiredLevel 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.

4.2 CTAP debug log evidence#

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 it
  • 7: {"rk": true} - resident key (discoverable credential) is enabled
  • 9: 2 - PIN protocol v2 was used for the PIN/UV negotiation
  • The authenticator flags byte 0xC5 confirms UP=1, UV=1, AT=1 - user was verified via PIN

The 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.

Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

5. Safari and Firefox: missing or limited credProtect support#

5.1 Safari ignores credProtect for roaming authenticators#

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:

  • Has default protection for credProtect
  • Has UV=false in the authenticator data
  • Works everywhere (because no browser or authenticator will refuse a Level 1 credential)
  • But provides minimal hardware-level security

5.2 Firefox: recent progress with credProtect#

Firefox historically lacked support for the credProtect extension. Testing shows the following extension support as reported by the browser:

BrowsercredPropscredentialProtectionPolicyenforceCredentialProtectionPolicy
Safaritruetruetrue
Firefox (pre-139)truefalsefalse
Firefox (139+)truetruetrue
Chrometruetruetrue

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.

6. Interoperability failure: Chrome-to-Safari gap#

The most common failure occurs when a security key registered in Chrome is subsequently used in Safari. Here is the step-by-step breakdown:

6.1 Registration in Chrome#

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.

6.2 Authentication in Safari#

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.

6.3 credProtect 2 vs credProtect 3#

From testing, the distinction is clear:

ScenariocredProtectCross-browser result
Chrome with UV=requiredLevel 2Works in Safari and Firefox
Chrome with UV=preferredLevel 3Fails 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 Testimonial

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 Trial

7. The credentialProtectionPolicy extension#

The 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, }, // ... }, };

7.1 Extension inputs#

Extension inputTypeDescription
credentialProtectionPolicystringOne of userVerificationOptional (experimental), userVerificationOptionalWithCredentialIDList or userVerificationRequired. Maps to CTAP values 0x01, 0x02 and 0x03 respectively
enforceCredentialProtectionPolicybooleanIf 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

7.2 Caveats#

  • Setting the policy to userVerificationOptional (Level 1) while requesting a discoverable credential may cause Chrome to override the request and still apply Level 2 to protect user privacy.
  • Setting enforceCredentialProtectionPolicy: true with userVerificationOptionalWithCredentialIDList on an authenticator that does not support credProtect (older CTAP 2.0 firmware) will cause the registration to fail entirely.
  • Safari ignores these extension inputs for roaming authenticators as of early 2026.

7.3 Browser support for extension inputs#

BrowserSends credentialProtectionPolicy to authenticator
ChromeYes
Firefox 139+Yes
SafariNo
Edge (Windows)Yes

8. Recommendations for Relying Parties#

There are two primary approaches depending on your deployment constraints:

8.1 Option 1: use 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", }, // ... }, };

8.2 Option 2: keep 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:

  • After registration, check the UV flag in the authenticator data. If UV is false, store the credential with a reduced trust level.
  • Use getClientExtensionResults() to check whether credProtect was applied and at which level. If the extension was ignored (as in Safari), flag the credential accordingly.
  • At login time, require an additional factor (e.g. password) for credentials that were created without user verification.

This gives you the error-rate benefits of UV=preferred for platform passkeys while gracefully handling the security key edge case.

Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

9. Conclusion#

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:

  • Chrome silently escalates to Level 3 when residentKey: "required" is combined with userVerification: "preferred", creating credentials that require UV for every operation
  • Safari does not send the extension at all and does not prompt for PIN creation with UV=preferred
  • Firefox 139+ brought credProtect support closer to parity with Chrome but with less implicit escalation

For 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.

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start Free Trial

Share this article


LinkedInTwitterFacebook