hcengineering/platform · error · TokenError

Token expired

Error message

Token expired

What it means

verifyToken decodes with signature verification, then checks the token's exp claim via isTokenExpired. Since decodeToken only verifies the signature, expiry is enforced here; an expired token throws a TokenError('Token expired').

Source

Thrown at foundations/core/packages/token/src/token.ts:222

    // do not know whether this token still stands, so refuse it rather than let a
    // revoked token survive by making the account unreachable. A verdict from
    // within the TTL is still trusted, which keeps brief outages from cutting off
    // healthy tokens mid-flight.
    throw new TokenError('Token revocation could not be verified')
  }
}

/**
 * Decodes and fully validates a token: signature (via {@link decodeToken}),
 * expiry, and — for revokable API tokens — revocation. Reuse this instead of
 * `decodeToken` anywhere expired or revoked tokens must be rejected (transactor
 * REST API, blob access, etc.) so the policy lives in one place.
 * @public
 */
export async function verifyToken (token: string, secret?: string): Promise<Token> {
  const decoded = decodeToken(token, true, secret)
  if (isTokenExpired(decoded)) {
    throw new TokenError('Token expired')
  }
  const apiTokenId = decoded.extra?.apiTokenId
  if (apiTokenId !== undefined && apiTokenRevocationChecker !== undefined) {
    if (await isApiTokenRevoked(apiTokenId, decoded, token, Date.now())) {
      throw new TokenError('Token revoked')
    }
  }
  return decoded
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Obtain a fresh token (re-authenticate or use a refresh flow)
  2. Check expiry client-side via isTokenExpired before sending requests and refresh proactively
  3. Verify system clocks are synchronized (NTP) on issuer and verifier
  4. Issue longer-lived tokens or implement refresh tokens if expiry is too short

Example fix

// before
const token = cachedToken
const t = await verifyToken(token) // may be expired
// after
const token = cachedToken
if (isTokenExpired(decodeToken(token, false))) {
  cachedToken = await refreshToken()
}
const t = await verifyToken(cachedToken)
Defensive patterns

Strategy: validation

Validate before calling

const decoded = decodeToken(token, false) // signature still checked with verify=true in real flow
if (isTokenExpired(decoded)) {
  await refreshToken()
}

Type guard

function isTokenUsable(t: Token): boolean {
  return !isTokenExpired(t)
}

Try / catch

try {
  const t = await verifyToken(token)
} catch (e) {
  if (e instanceof TokenError && e.message === 'Token expired') {
    await refreshSession()
    return verifyToken(newToken)
  }
  throw e
}

Prevention

When it happens

Trigger: Calling verifyToken (directly or via decoded/withSession) with a token whose exp (seconds since epoch) is earlier than the current time — including expired API tokens that would otherwise pass signature verification.

Common situations: Long-lived client sessions using stale tokens, clocks skewing between issuer and consumer, cached tokens past their expiry, forgetting to refresh tokens before requests.

Understand the failure class

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/47255c65bd3075f9. Report an issue: GitHub.