QuantumNous/new-api · error · Error

Passkey verification failed

Error message

Passkey verification failed

What it means

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.

Source

Thrown at web/src/features/auth/secure-verification/api.ts:167

      throw new Error(i18next.t('Verification flow expired'))
    }

    const credential = (await navigator.credentials.get({
      publicKey,
    })) as PublicKeyCredential | null

    if (!credential) {
      throw new Error(i18next.t('Passkey verification was cancelled'))
    }

    const assertion = buildAssertionResult(credential)
    if (!assertion) {
      throw new Error(i18next.t('Unable to build Passkey assertion'))
    }

    const finishResponse = await finishPasskeyVerification(flowToken, assertion)
    if (!finishResponse.success) {
      throw new Error(
        finishResponse.message || i18next.t('Passkey verification failed')
      )
    }

    if (!finishResponse.data?.proof_token) {
      throw new Error(i18next.t('Verification proof was not returned'))
    }
    return finishResponse.data
  } catch (error: unknown) {
    if (error instanceof DOMException && error.name === 'NotAllowedError') {
      throw new Error(
        i18next.t('Passkey verification was cancelled or timed out'),
        { cause: error }
      )
    }
    if (error instanceof DOMException && error.name === 'InvalidStateError') {
      throw new Error(
        i18next.t('Passkey verification is not available in the current state'),

View on GitHub (pinned to e2c7aa7b10)

Solutions

  1. Read the finish endpoint's response message in DevTools for the precise server-side reason.
  2. Verify backend RP ID and origin configuration exactly matches the site's domain and scheme.
  3. If running multiple backend replicas, ensure the flow/verification store (e.g. Redis) is shared, not in-memory per instance.
  4. Restart the flow (fresh begin) if the flow token simply expired.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const proof = await verify('passkey', scope)
  return proof.proof_token
} catch (e) {
  const msg = getErrorMessage(e)
  if (/expired|flow/i.test(msg)) {
    return await verify('passkey', scope) // fresh begin/finish
  }
  toast.error(msg)
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of QuantumNous/new-api@e2c7aa7b10 (2026-08-15). Data as JSON: /api/errors/59c31c4b2544b6b6. Report an issue: GitHub.