{"record":{"id":"cb0cf67628702fd8","repo":"QuantumNous/new-api","slug":"verification-flow-expired","errorCode":null,"errorMessage":"Verification flow expired","messagePattern":"Verification flow expired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/features/auth/secure-verification/api.ts","lineNumber":149,"sourceCode":"    throw new Error(\n      i18next.t('Passkey verification is not supported in this environment')\n    )\n  }\n\n  try {\n    const beginResponse = await beginPasskeyVerification(scope)\n    if (!beginResponse.success) {\n      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(","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/auth/secure-verification/api.ts#L131-L167","documentation":"Thrown in verifyPasskey() when the begin endpoint succeeds but the response data contains no flow_token. The passkey flow is two-phase (begin → finish) and the flow token links them; without it the finish call cannot be made. The code treats a missing flow token as an expired/invalid verification flow.","triggerScenarios":"Begin endpoint returns success:true with data lacking flow_token (version skew or response-shape change); flow store on the backend evicted the token before the response was serialized; a proxy or interceptor rewriting the response body.","commonSituations":"Frontend/backend version mismatch during deploys; backend reads flow_token from data.options.flow_token but frontend expects data.flow_token after a shape refactor.","solutions":["Log the full beginResponse.data and locate where flow_token actually lives.","Align frontend and backend versions so the begin response contract matches.","If the token sits at a different path after a backend change, update beginResponse.data?.flow_token accordingly.","Retry the flow — transient in-memory flow-store eviction resolves on a fresh begin."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"const hasFlowToken = (\n  d: unknown\n): d is { flow_token: string } =>\n  typeof d === 'object' &&\n  d !== null &&\n  typeof (d as { flow_token?: unknown }).flow_token === 'string'","tryCatchPattern":"try {\n  return await verify('passkey', scope)\n} catch (e) {\n  if (/flow expired/i.test(getErrorMessage(e))) {\n    return await verify('passkey', scope) // one fresh begin/finish cycle\n  }\n  throw e\n}","preventionTips":["Pin frontend/backend versions during deploys — flow_token placement is a contract","Retry once on missing flow_token; begin creates a fresh flow","Log beginResponse.data shape when the token is absent to catch drift early"],"tags":["passkey","webauthn","flow-token","contract"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}