hcengineering/platform · error · TokenError

Token revocation could not be verified

Error message

Token revocation could not be verified

What it means

For revokable API tokens, isApiTokenRevoked asks the account service whether the token was revoked. If the account service cannot be reached, revocation status is unknown, and the code deliberately refuses the token (fail-closed) rather than letting a possibly-revoked token pass, while still trusting cached verdicts within the TTL.

Source

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

}

async function isApiTokenRevoked (apiTokenId: string, token: Token, raw: string, now: number): Promise<boolean> {
  const cached = revocationCache.get(apiTokenId)
  if (cached !== undefined && now - cached.checkedAt <= REVOCATION_CACHE_TTL_MS) {
    return cached.revoked
  }

  try {
    const revoked = await (apiTokenRevocationChecker as ApiTokenRevocationChecker)(apiTokenId, token, raw)
    cacheRevocation(apiTokenId, revoked, now)
    return revoked
  } catch {
    // The account is the only authority on revocation. If it cannot be reached we
    // 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())) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Restore connectivity to the account service and retry verification
  2. Check the account service health/endpoint configuration
  3. Increase or rely on the revocation-verdict TTL so brief outages don't break healthy tokens
  4. As a caller, catch TokenError and surface 'try again shortly' rather than treating it as a bad credential

Example fix

// before
const token = await verifyToken(raw) // throws during account-service blip
// after
try {
  const token = await verifyToken(raw)
} catch (e) {
  if (e.message === 'Token revocation could not be verified') return retryLater()
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No caller-side check can confirm revocation; precondition is account-service reachability
await healthCheck(accountServiceUrl) // fail fast if revocation authority is unreachable

Try / catch

try {
  const t = await verifyToken(token)
} catch (e) {
  if (e instanceof TokenError && e.message === 'Token revocation could not be verified') {
    // fail-closed: treat as temporary unavailability, not bad credentials
    return res.status(503).send('auth backend unavailable, retry shortly')
  }
  throw e
}

Prevention

When it happens

Trigger: verifyToken on a token with extra.apiTokenId when the account/revocation service call throws — network outage, DNS failure, timeout, or service crash.

Common situations: Partial outages where auth works but the account service is down; network partitions; misconfigured account service endpoint; brief blips during deploys (mitigated by the TTL cache).

Related errors


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