When to Use Resident vs Discoverable Credentials
Direct resolution of credential type selection for WebAuthn implementations requires clarifying a common architectural misconception: in modern FIDO2 specifications, resident and discoverable credentials are functionally synonymous. The distinction exists solely in API versioning and authenticator capability flags. Selecting the correct configuration dictates whether your relying party (RP) can support username-less authentication, how credential IDs are routed during assertion, and whether your compliance posture aligns with current CTAP2.1 requirements.
Spec Mapping & API Evolution (WebAuthn Level 2 vs Level 3)
The W3C specification transitioned from residentKey (Level 2) to discoverableCredentials (Level 3) to strictly align with CTAP2.1 authenticator behavior. Misalignment between browser implementations and authenticator firmware triggers silent fallbacks to non-resident flows, breaking username-less UX expectations. Understanding how these flags map to actual key storage behavior requires a baseline grasp of Public Key vs Symmetric Credential Types.
Diagnostic & Error Resolution
| Symptom | Error Code | Root Cause | Remediation |
|---|---|---|---|
| Registration fails immediately | TypeError: Failed to execute 'create' on 'PublicKeyCredential': Invalid residentKey requirement |
Legacy residentKey: 'required' passed to browsers expecting discoverableCredentials: true |
Normalize payload using feature detection before invoking navigator.credentials.create() |
| Silent downgrade to non-resident | No explicit error; credential ID returned in registration response | Authenticator firmware mismatch (CTAP2.0 vs CTAP2.1) or missing discoverableCredentials flag |
Map residentKey enum to boolean discoverableCredentials for cross-browser compatibility |
Diagnostic Command:
// Inspect resolved authenticator selection in browser DevTools console
const resolved = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
console.log('Platform supports resident keys:', resolved);
Production Patch:
// Dual-flag fallback for cross-version compatibility
const options = {
publicKey: {
rp: { name: 'Example Corp', id: 'example.com' },
user: { id: Uint8Array.from('user123', c => c.charCodeAt(0)), name: '[email protected]', displayName: 'User' },
challenge: crypto.getRandomValues(new Uint8Array(32)),
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
authenticatorSelection: {
discoverableCredentials: true, // WebAuthn L3 / CTAP2.1
residentKey: 'required', // WebAuthn L2 fallback
userVerification: 'required'
}
}
};
Debugging NotAllowedError & InvalidStateError During Discovery
When discoverableCredentials is enabled but the authenticator lacks secure element capacity or violates platform constraints, the browser returns NotAllowedError or InvalidStateError. This typically occurs when the RP requests credential discovery without verifying authenticatorAttachment or userVerification constraints, or attempts duplicate registration.
Diagnostic & Error Resolution
| Symptom | Error Code | Root Cause | Remediation |
|---|---|---|---|
| Discovery prompt dismissed or blocked | NotAllowedError (DOMException) |
RP requests discovery on a roaming authenticator without cross-device sync capability | Implement conditional UI flow checking platform availability before invoking discovery |
| Registration collision | InvalidStateError: The credential already exists |
Duplicate credential registration attempt without prior get() assertion check |
Query existing credentials via navigator.credentials.get() before create() to prevent collision |
Diagnostic Command:
# Browser DevTools Console: Verify conditional mediation support
PublicKeyCredential.isConditionalMediationAvailable().then(console.log);
// Returns true if autofill/discovery UI can be injected natively
Production Patch:
async function safeRegister(publicKeyOptions) {
try {
// Pre-flight check to prevent InvalidStateError collisions
await navigator.credentials.get({ publicKey: { ...publicKeyOptions, allowCredentials: [] } });
} catch (err) {
if (err.name === 'InvalidStateError') {
console.warn('Credential already provisioned. Trigger assertion flow instead.');
return handleExistingCredential();
}
if (err.name === 'NotAllowedError') {
throw new Error('User verification or platform constraints blocked discovery.');
}
throw err;
}
return navigator.credentials.create({ publicKey: publicKeyOptions });
}
Configuration Matrix for Identity Platform Builders
Select credential type based on threat model, UX requirements, and storage architecture. Resident/discoverable credentials enable true username-less authentication but require secure element or platform-bound storage. Non-resident credentials rely on server-side credential ID mapping and explicit user identification during login.
| Requirement | Recommended Configuration | Storage Behavior | Compliance Impact |
|---|---|---|---|
| Username-less login | discoverableCredentials: true |
Authenticator stores private key + user handle locally | Requires FIDO2 Level 1+ certification; aligns with phishing-resistant MFA mandates |
| Cross-device sync (Passkeys) | discoverableCredentials: true + userVerification: 'required' |
Cloud-synced secure enclave (iCloud Keychain, Google Password Manager) | Meets NIST SP 800-63B AAL2/AAL3; audit trail required for credential provisioning |
| High-volume enterprise SSO | discoverableCredentials: false |
RP stores credential ID in user directory; authenticator only holds private key | Reduces authenticator storage limits; requires robust server-side credential routing |
For complete protocol handshake validation and architectural alignment, reference WebAuthn & FIDO2 Protocol Fundamentals.
Step-by-Step Compliance & Migration Fix
Legacy implementations often hardcode residentKey: 'discouraged' or mix credential types within the same RP ID scope, triggering SecurityError during discovery. Audit existing registration payloads and enforce dynamic evaluation based on authenticatorSelection.requireResidentKey. Validate against FIDO2 certification logs to ensure CTAP2.1 compliance.
Diagnostic & Error Resolution
| Symptom | Error Code | Root Cause | Remediation |
|---|---|---|---|
| Assertion fails after migration | SecurityError: The operation is insecure |
Mixed credential types in same RP ID scope | Enforce consistent discoverableCredentials flag across all registration payloads |
| Discovery prompt fails silently | NotAllowedError / SecurityError |
Missing userVerification flag during discovery request |
Enforce userVerification: 'required' for discoverable flows |
| Database collision on login | IntegrityError / DuplicateKey |
Missing credential ID deduplication at RP layer | Implement credential ID deduplication at the RP database layer |
Production Patch:
// Dynamic configuration based on platform capability & compliance posture
const isDiscoverableSupported =
PublicKeyCredential.isConditionalMediationAvailable?.() ?? false;
const residentConfig = isDiscoverableSupported
? { discoverableCredentials: true, userVerification: 'required' }
: { discoverableCredentials: false, userVerification: 'preferred' };
const registrationOptions = {
publicKey: {
rp: { id: window.location.hostname, name: 'SecureApp' },
user: { id: new Uint8Array(16), name: '[email protected]', displayName: 'User' },
challenge: crypto.getRandomValues(new Uint8Array(32)),
pubKeyCredParams: [{ alg: -7, type: 'public-key' }, { alg: -257, type: 'public-key' }],
authenticatorSelection: {
...residentConfig,
residentKey: residentConfig.discoverableCredentials ? 'required' : 'discouraged'
}
}
};
Compliance Verification Steps:
- Run
PublicKeyCredential.isConditionalMediationAvailable()to validate autofill compatibility. - Inspect
authenticatorDataflags in registration response; verifyrk(resident key) bit is set whendiscoverableCredentials: true. - Cross-reference RP ID scope in your credential database; ensure no mixed
residentKey/discoverableCredentialsstates exist for identical user handles. - Validate against FIDO Alliance certification logs to confirm CTAP2.1
GetAssertiondiscovery routing matches your implementation.