dotnet/aspnetcore · warning · Error

Some passkey features are missing. Please update your…

Error message

Some passkey features are missing. Please update your browser.

What it means

Thrown by obtainCredential when the module-level browserSupportsPasskeys constant is false. That constant checks that window.PublicKeyCredential exists AND that the static parseCreationOptionsFromJSON / parseRequestOptionsFromJSON methods are present — the latter require modern browsers (Chrome ~116+, Safari 16+, Firefox 122+). The browser lacks full WebAuthn conditional-meditation + JSON-parsing support.

Solutions

  1. Detect browserSupportsPasskeys at component render time and hide/disable the passkey submit button instead of letting the user trigger the throw.
  2. Render a fallback login mechanism (password, TOTP, email link) when browserSupportsPasskeys is false.
  3. Guide the user to update their browser; the message itself recommends this.
  4. For automated testing, use a virtual authenticator (e.g. Playwright/WebdriverIO virtual authenticator) so the WebAuthn surface is present.

Example fix

// before
if (!browserSupportsPasskeys) {
  throw new Error('Some passkey features are missing. Please update your browser.');
}

// after — gate the UI on the same constant and render fallback
// in the razor component:
// <button type="submit" disabled="@(!BrowserSupportsPasskeys)">Use passkey</button>
// @if (!BrowserSupportsPasskeys) { <PasswordLogin /> }
Defensive patterns

Strategy: type-guard

Validate before calling

export function browserSupportsPasskeys(): boolean {
  return typeof window !== 'undefined'
    && typeof window.PublicKeyCredential !== 'undefined'
    && typeof (window.PublicKeyCredential as any).parseCreationOptionsFromJSON === 'function'
    && typeof (window.PublicKeyCredential as any).parseRequestOptionsFromJSON === 'function'
    && typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function';
}

Type guard

function supportsPasskeys(g: typeof globalThis): g is typeof globalThis & {
  PublicKeyCredential: typeof PublicKeyCredential & {
    parseCreationOptionsFromJSON: Function;
    parseRequestOptionsFromJSON: Function;
  };
} {
  return typeof g.PublicKeyCredential !== 'undefined'
    && typeof g.PublicKeyCredential.parseCreationOptionsFromJSON === 'function'
    && typeof g.PublicKeyCredential.parseRequestOptionsFromJSON === 'function';
}

Try / catch

if (!browserSupportsPasskeys()) {
  renderFallbackAuth(); // never invoke obtainCredential
} else {
  try { await component.obtainAndSubmitCredential(); } catch (e) { /* ... */ }
}

Prevention

When it happens

Trigger: Calling obtainCredential (via obtainAndSubmitCredential, e.g. on form submit) in a browser where window.PublicKeyCredential is undefined, or where the parse*FromJSON static methods are missing. This includes all IE, old Safari (<16), old Chrome, and embedded WebViews.

Common situations: User on an older mobile WebView; enterprise-locked browsers with WebAuthn disabled; testing in headless browsers without virtual authenticators; deploying the passkey component to a population that includes legacy browsers without a fallback auth method.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/03bbcae95c62b779. Report an issue: GitHub.

Appendix: source

Thrown at src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor.js:67

        };

        this.internals.form.addEventListener('submit', (event) => {
            if (event.submitter?.name === '__passkeySubmit') {
                event.preventDefault();
                this.obtainAndSubmitCredential();
            }
        });

        this.tryAutofillPasskey();
    }

    disconnectedCallback() {
        this.abortController?.abort();
    }

    async obtainCredential(useConditionalMediation, signal) {
        if (!browserSupportsPasskeys) {
            throw new Error('Some passkey features are missing. Please update your browser.');
        }

        if (this.attrs.operation === 'Create') {
            return await createCredential(signal);
        } else if (this.attrs.operation === 'Request') {
            const email = new FormData(this.internals.form).get(this.attrs.emailName);
            const mediation = useConditionalMediation ? 'conditional' : undefined;
            return await requestCredential(email, mediation, signal);
        } else {
            throw new Error(`Unknown passkey operation '${this.attrs.operation}'.`);
        }
    }

    async obtainAndSubmitCredential(useConditionalMediation = false) {
        this.abortController?.abort();
        this.abortController = new AbortController();
        const signal = this.abortController.signal;
        const formData = new FormData();

View on GitHub (pinned to 3600ca084e)