dotnet/aspnetcore · error · Error
Unknown passkey operation
Error message
Unknown passkey operation '${this.attrs.operation}'. What it means
Thrown by obtainCredential when this.attrs.operation is neither 'Create' nor 'Request'. The component is parameterized by an operation attribute set from the Razor host; an unrecognized value indicates a configuration or integration bug — not a runtime user error.
Solutions
- Inspect the rendered PasskeySubmit element's data attributes to see the actual operation value being passed.
- Ensure the Razor component sets operation to exactly 'Create' or 'Request' (case-sensitive).
- If you extended the protocol with a new operation, update the if/else chain in obtainCredential to handle it before the throw.
- Add a type-guard or assertion at component initialization so misconfiguration surfaces at render, not on submit.
Example fix
// before
} else {
throw new Error(`Unknown passkey operation '${this.attrs.operation}'.`);
}
// after — fail fast at init with an allowlist
const ALLOWED_OPS = new Set(['Create', 'Request']);
if (!ALLOWED_OPS.has(this.attrs.operation)) {
throw new Error(`Invalid operation '${this.attrs.operation}'. Expected one of: ${[...ALLOWED_OPS].join(', ')}`);
} Defensive patterns
Strategy: validation
Validate before calling
const PASSKEY_OPERATIONS = new Set(['Create', 'Request']);
function isValidOperation(op: unknown): op is 'Create' | 'Request' {
return typeof op === 'string' && PASSKEY_OPERATIONS.has(op);
}
// assert at component attach:
if (!isValidOperation(this.attrs.operation)) {
throw new TypeError(`operation must be 'Create' or 'Request', got ${this.attrs.operation}`);
} Type guard
function isPasskeyOperation(v: unknown): v is 'Create' | 'Request' {
return v === 'Create' || v === 'Request';
} Try / catch
try {
await obtainCredential(...);
} catch (e) {
if (/Unknown passkey operation/.test(e.message)) {
console.error('Component misconfigured — operation attr:', this.attrs.operation);
} else throw e;
} Prevention
- Validate operation at component initialization, not on submit.
- Treat operation as a closed enum shared between Razor and JS.
- Add a unit test asserting the attribute matches the JS allowlist.
When it happens
Trigger: The PasskeySubmit razor component passes an operation value that is not exactly 'Create' or 'Request' (case-sensitive). This happens if the attribute is left unset (undefined), mis-cased ('create'), or set to a new operation that the JS does not yet handle.
Common situations: Customizing the Blazor account components and renaming the operation enum; upgrading the template where a new operation was added server-side but the JS file was not updated; copy-paste of the component without wiring the operation attribute.
Related errors
- ' ' is flagged with SingleDelivery, but the selected…
- A valid url is required.
- assembly must be defined when using a descriptor.
- Authentication refreshBeforeExpirationInMilliseconds must…
- Cannot have empty query parameter names.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/10423f02b4be9450.
Report an issue: GitHub.
Appendix: source
Thrown at src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Components/Account/Shared/PasskeySubmit.razor.js:77
}
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();
try {
const credential = await this.obtainCredential(useConditionalMediation, signal);
const credentialJson = JSON.stringify(credential);
formData.append(`${this.attrs.name}.CredentialJson`, credentialJson);
} catch (error) {
if (error.name === 'AbortError') {
// The user explicitly canceled the operation - return without error.
return;
}
console.error(error);View on GitHub (pinned to 3600ca084e)