Wei-Shaw/sub2api · warning

Passkey creation was cancelled

Error message

Passkey creation was cancelled

What it means

In frontend/src/api/passkey.ts:135, after navigator.credentials.create({publicKey}) for passkey registration, the code requires the resolved value to be an instanceof PublicKeyCredential. When the browser returns null (ceremony aborted in browsers that resolve rather than reject) or a non-PublicKeyCredential object, the code throws 'Passkey creation was cancelled'. Like sign-in, most explicit user cancellations reject with NotAllowedError from credentials.create() itself.

Source

Thrown at frontend/src/api/passkey.ts:135

  }
  const { data } = await apiClient.post<AuthResponse>('/auth/passkey/login/finish', {
    session_token: begin.session_token,
    credential: serializeAssertionCredential(credential)
  })
  return data
}

async function register(name: string, password: string): Promise<PasskeyCredentialSummary> {
  requirePasskeySupport()
  const { data: begin } = await apiClient.post<CeremonyOptionsResponse>(
    '/user/passkeys/register/begin',
    { password }
  )
  const credential = await navigator.credentials.create({
    publicKey: creationOptionsFromJSON(begin.options.publicKey)
  })
  if (!(credential instanceof PublicKeyCredential)) {
    throw new Error('Passkey creation was cancelled')
  }
  const { data } = await apiClient.post<PasskeyCredentialSummary>(
    '/user/passkeys/register/finish',
    {
      session_token: begin.session_token,
      name,
      credential: serializeRegistrationCredential(credential)
    }
  )
  return data
}

async function list(): Promise<PasskeyCredentialSummary[]> {
  const { data } = await apiClient.get<PasskeyCredentialSummary[]>('/user/passkeys')
  return data
}

async function rename(id: number, name: string): Promise<void> {

View on GitHub (pinned to 073e92d171)

Solutions

  1. Treat this error and NotAllowedError identically in the UI: show 'registration cancelled, try again' rather than a generic failure.
  2. Validate creationOptionsFromJSON(begin.options.publicKey) output includes user.id, user.name, rp.id, challenge and pubKeyCredParams — malformed options cause silent aborts in some browsers.
  3. In E2E tests, use virtual authenticators (Playwright/Chrome --enable-features or CDP WebAuthn) instead of mocking create().
  4. Refactor to `if (!credential || !(credential instanceof PublicKeyCredential))` for clearer null handling.

Example fix

// before
const credential = await navigator.credentials.create({ publicKey: creationOptionsFromJSON(begin.options.publicKey) })
if (!(credential instanceof PublicKeyCredential)) {
  throw new Error('Passkey creation was cancelled')
}

// after
const credential = await navigator.credentials.create({ publicKey: creationOptionsFromJSON(begin.options.publicKey) })
if (!credential || typeof credential.id === 'undefined') {
  throw new Error('Passkey creation was cancelled')
}
// narrow via duck-typing instead of instanceof so mocks/edge browsers still pass
if (credential && !(credential instanceof PublicKeyCredential) && !('rawId' in credential)) {
  throw new Error('Passkey creation was cancelled')
}
Defensive patterns

Strategy: try-catch

Type guard

function hasRegistrationShape(c: Credential | null): c is PublicKeyCredential {
  return !!c && 'rawId' in c && 'response' in c;
}

Try / catch

try {
  await registerPasskey(name, password);
} catch (e) {
  if (e.name === 'NotAllowedError' || e.message === 'Passkey creation was cancelled') {
    showInfo('Registration cancelled — try again'); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Registering a passkey where credentials.create() resolves null: dismissed Windows Hello / Touch ID sheet in certain browser builds, WebView without a proper WebAuthn UI, or unit tests mocking create() with null/generic objects. Also triggered by a resident-credential request when the authenticator silently fails to create one and the browser resolves empty.

Common situations: E2E tests (Cypress historically cannot drive real WebAuthn); users on Android WebView-based browsers; users cancelling the save-prompt for a passkey; rp.name/user.name fields missing from creation options causing some browsers to abort.

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/4e901c4757b0bdcb. Report an issue: GitHub.