{"record":{"id":"bda6974f3cbe6815","repo":"QuantumNous/new-api","slug":"passkey-verification-was-cancelled","errorCode":null,"errorMessage":"Passkey verification was cancelled","messagePattern":"Passkey verification was cancelled","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"web/src/features/auth/secure-verification/api.ts","lineNumber":157,"sourceCode":"      throw new Error(\n        beginResponse.message || i18next.t('Failed to start verification')\n      )\n    }\n\n    const publicKey = prepareCredentialRequestOptions(\n      beginResponse.data?.options ?? beginResponse.data\n    )\n    const flowToken = beginResponse.data?.flow_token\n    if (!flowToken) {\n      throw new Error(i18next.t('Verification flow expired'))\n    }\n\n    const credential = (await navigator.credentials.get({\n      publicKey,\n    })) as PublicKeyCredential | null\n\n    if (!credential) {\n      throw new Error(i18next.t('Passkey verification was cancelled'))\n    }\n\n    const assertion = buildAssertionResult(credential)\n    if (!assertion) {\n      throw new Error(i18next.t('Unable to build Passkey assertion'))\n    }\n\n    const finishResponse = await finishPasskeyVerification(flowToken, assertion)\n    if (!finishResponse.success) {\n      throw new Error(\n        finishResponse.message || i18next.t('Passkey verification failed')\n      )\n    }\n\n    if (!finishResponse.data?.proof_token) {\n      throw new Error(i18next.t('Verification proof was not returned'))\n    }\n    return finishResponse.data","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/auth/secure-verification/api.ts#L139-L175","documentation":"Thrown in verifyPasskey() when navigator.credentials.get() resolves to null instead of throwing. Per the WebAuthn spec the promise can resolve null when no credential is produced, which in practice corresponds to the user dismissing or aborting the picker without a DOMException. It is a user-abort condition, not a system failure.","triggerScenarios":"User cancels the browser's passkey/authenticator selection dialog in a way that resolves null; browser quirk where dismissal resolves instead of rejecting with NotAllowedError.","commonSituations":"User closes the OS-level WebAuthn sheet; user presses Escape on some platforms; conditional-mediation autofill dismissed.","solutions":["Treat this as user cancellation: return the user to the verification choice instead of showing an error.","Offer the 2FA code method as an alternative after cancellation.","No system fix is needed unless it fires without user action — then check for malformed publicKey options (bad rpId, empty allowList, invalid challenge)."],"exampleFix":"// before\ncatch (e) { toast.error(getErrorMessage(e)) }\n\n// after — classify cancellation as non-error\ntry {\n  await verify('passkey', scope)\n} catch (e) {\n  if (/cancelled/i.test(getErrorMessage(e))) {\n    return // user aborted, back to method selection\n  }\n  toast.error(getErrorMessage(e))\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await verify('passkey', scope)\n} catch (e) {\n  if (/cancelled/i.test(getErrorMessage(e))) return // silent user abort\n  toast.error(getErrorMessage(e))\n}","preventionTips":["Treat null-credential results as user cancellation, not errors","Route back to method selection (offer 2FA) after cancellation","Validate publicKey options (rpId, challenge, allowList) if this fires without user action"],"tags":["passkey","webauthn","user-cancellation","frontend"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}