tinyhumansai/openhuman · critical

Login token invalid or expired

Error message

Login token invalid or expired

What it means

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.

Source

Thrown at app/src/services/api/authApi.ts:60

    throw error;
  } finally {
    window.clearTimeout(timeoutId);
  }
}

/**
 * Consume a verified login token and return the JWT.
 * Works for both Telegram and OAuth login tokens.
 * POST /telegram/login-tokens/:token/consume (no auth required)
 */
export async function consumeLoginToken(loginToken: string): Promise<string> {
  const response = await callCoreRpc<{ result: { jwtToken: string } }>({
    method: 'openhuman.auth.consume_login_token',
    params: { loginToken },
  });
  const jwtToken = response.result?.jwtToken;
  if (!jwtToken) {
    throw new Error('Login token invalid or expired');
  }
  return jwtToken;
}

View on GitHub (pinned to a221052e0d)

Solutions

  1. Restart the login flow to mint a fresh token (request a new magic link / re-auth via Telegram)
  2. Ensure the deep-link/token handler is idempotent-guarded so the same token isn't consumed twice by competing listeners
  3. Check core logs for the consume_login_token outcome to distinguish 'used' vs 'expired' vs 'unknown'
  4. If it persists for every fresh token, check version skew between frontend and core envelope shapes

Example fix

// before
const jwt = await consumeLoginToken(token);
setSession(jwt);

// after
let jwt: string;
try {
  jwt = await consumeLoginToken(token);
} catch {
  showLoginError('This login link is invalid or expired. Request a new one.');
  navigateToLogin();
  return;
}
setSession(jwt);
Defensive patterns

Strategy: try-catch

Type guard

const hasJwt = (r: unknown): r is { result: { jwtToken: string } } =>
  typeof (r as { result?: { jwtToken?: unknown } })?.result?.jwtToken === 'string' &&
  ((r as { result: { jwtToken: string } }).result.jwtToken.length > 0);

Try / catch

try { const jwt = await consumeLoginToken(token); setSession(jwt); }
catch (e) {
  if (String((e as Error).message).includes('invalid or expired')) {
    showLoginError('This link has expired. Request a new login link.'); navigateToLogin();
  } else throw e;
}

Prevention

When it happens

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

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

Understand the failure class

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/9b222b18f825ccd5. Report an issue: GitHub.