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
- Detect browserSupportsPasskeys at component render time and hide/disable the passkey submit button instead of letting the user trigger the throw.
- Render a fallback login mechanism (password, TOTP, email link) when browserSupportsPasskeys is false.
- Guide the user to update their browser; the message itself recommends this.
- 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
- Gate passkey UI on the same constant the JS uses.
- Always offer a non-passkey fallback authentication path.
- For automated tests, run under a browser/profile that exposes a virtual authenticator.
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
- Please enter a username.
- The server responded with status
- Unknown action
- Unknown passkey operation
- ' ' is flagged with SingleDelivery, but the selected…
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)