{"record":{"id":"9b222b18f825ccd5","repo":"tinyhumansai/openhuman","slug":"login-token-invalid-or-expired","errorCode":null,"errorMessage":"Login token invalid or expired","messagePattern":"Login token invalid or expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"app/src/services/api/authApi.ts","lineNumber":60,"sourceCode":"    throw error;\n  } finally {\n    window.clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Consume a verified login token and return the JWT.\n * Works for both Telegram and OAuth login tokens.\n * POST /telegram/login-tokens/:token/consume (no auth required)\n */\nexport async function consumeLoginToken(loginToken: string): Promise<string> {\n  const response = await callCoreRpc<{ result: { jwtToken: string } }>({\n    method: 'openhuman.auth.consume_login_token',\n    params: { loginToken },\n  });\n  const jwtToken = response.result?.jwtToken;\n  if (!jwtToken) {\n    throw new Error('Login token invalid or expired');\n  }\n  return jwtToken;\n}\n","sourceCodeStart":42,"sourceCodeEnd":64,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/authApi.ts#L42-L64","documentation":"consumeLoginToken called openhuman.auth.consume_login_token and the response's result.jwtToken was missing/empty. The core consumed (or rejected) the one-time login token but produced no JWT, which the client reports as the token being invalid or expired — the dominant real-world cause.","triggerScenarios":"Completing Telegram/OAuth login with a token already used once (one-time consumption), a token past its TTL, a truncated/copy-mangled token string, or a core version whose envelope differs so jwtToken is read as undefined.","commonSituations":"User clicks an old login link (reopening a stale email/Telegram message); double-processing the same deep link by two handlers; clock skew or long delay between link issue and click; dev flow against a reset database where the token no longer exists.","solutions":["Restart the login flow to mint a fresh token (request a new magic link / re-auth via Telegram)","Ensure the deep-link/token handler is idempotent-guarded so the same token isn't consumed twice by competing listeners","Check core logs for the consume_login_token outcome to distinguish 'used' vs 'expired' vs 'unknown'","If it persists for every fresh token, check version skew between frontend and core envelope shapes"],"exampleFix":"// before\nconst jwt = await consumeLoginToken(token);\nsetSession(jwt);\n\n// after\nlet jwt: string;\ntry {\n  jwt = await consumeLoginToken(token);\n} catch {\n  showLoginError('This login link is invalid or expired. Request a new one.');\n  navigateToLogin();\n  return;\n}\nsetSession(jwt);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const hasJwt = (r: unknown): r is { result: { jwtToken: string } } =>\n  typeof (r as { result?: { jwtToken?: unknown } })?.result?.jwtToken === 'string' &&\n  ((r as { result: { jwtToken: string } }).result.jwtToken.length > 0);","tryCatchPattern":"try { const jwt = await consumeLoginToken(token); setSession(jwt); }\ncatch (e) {\n  if (String((e as Error).message).includes('invalid or expired')) {\n    showLoginError('This link has expired. Request a new login link.'); navigateToLogin();\n  } else throw e;\n}","preventionTips":["Mint a fresh token per login attempt instead of reusing deep links","Guard deep-link handlers so the same token is consumed exactly once","Complete the token exchange promptly — treat login links as short-lived"],"tags":["authentication","login-token","rpc","one-time-token"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}