{"record":{"id":"59c31c4b2544b6b6","repo":"QuantumNous/new-api","slug":"passkey-verification-failed","errorCode":null,"errorMessage":"Passkey verification failed","messagePattern":"Passkey verification failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/features/auth/secure-verification/api.ts","lineNumber":167,"sourceCode":"      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\n  } catch (error: unknown) {\n    if (error instanceof DOMException && error.name === 'NotAllowedError') {\n      throw new Error(\n        i18next.t('Passkey verification was cancelled or timed out'),\n        { cause: error }\n      )\n    }\n    if (error instanceof DOMException && error.name === 'InvalidStateError') {\n      throw new Error(\n        i18next.t('Passkey verification is not available in the current state'),","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/auth/secure-verification/api.ts#L149-L185","documentation":"Fallback thrown in verifyPasskey() when finishPasskeyVerification(flowToken, assertion) responds with success falsy and no message. This is the server rejecting the signed assertion at the end of the ceremony — signature verification failed, the flow token expired, or the challenge did not match. The server's message overrides the generic text when present.","triggerScenarios":"POST to the passkey finish endpoint with an assertion whose signature fails server-side verification; flow token expired between begin and finish; RP ID/origin mismatch between what the browser signed and what the backend validates; credential was deleted server-side between registration steps.","commonSituations":"Backend WebAuthn origin/RPID misconfigured after domain or port change; user sat on the dialog past the flow-token TTL; load-balanced backend where the flow store is not shared between begin and finish requests.","solutions":["Read the finish endpoint's response message in DevTools for the precise server-side reason.","Verify backend RP ID and origin configuration exactly matches the site's domain and scheme.","If running multiple backend replicas, ensure the flow/verification store (e.g. Redis) is shared, not in-memory per instance.","Restart the flow (fresh begin) if the flow token simply expired."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const proof = await verify('passkey', scope)\n  return proof.proof_token\n} catch (e) {\n  const msg = getErrorMessage(e)\n  if (/expired|flow/i.test(msg)) {\n    return await verify('passkey', scope) // fresh begin/finish\n  }\n  toast.error(msg)\n  throw e\n}","preventionTips":["Keep backend RP ID and origin exactly matching the served domain/scheme","Share the flow store (Redis) across replicas so begin/finish can land on different nodes","Restart the whole ceremony rather than reusing a flow token after delays"],"tags":["passkey","webauthn","api-response","signature-verification"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}