{"record":{"id":"78e74c9e0a46d8ae","repo":"hcengineering/platform","slug":"token-revoked","errorCode":null,"errorMessage":"Token revoked","messagePattern":"Token revoked","errorType":"exception","errorClass":"TokenError","httpStatus":null,"severity":"error","filePath":"foundations/core/packages/token/src/token.ts","lineNumber":227,"sourceCode":"  }\n}\n\n/**\n * Decodes and fully validates a token: signature (via {@link decodeToken}),\n * expiry, and — for revokable API tokens — revocation. Reuse this instead of\n * `decodeToken` anywhere expired or revoked tokens must be rejected (transactor\n * REST API, blob access, etc.) so the policy lives in one place.\n * @public\n */\nexport async function verifyToken (token: string, secret?: string): Promise<Token> {\n  const decoded = decodeToken(token, true, secret)\n  if (isTokenExpired(decoded)) {\n    throw new TokenError('Token expired')\n  }\n  const apiTokenId = decoded.extra?.apiTokenId\n  if (apiTokenId !== undefined && apiTokenRevocationChecker !== undefined) {\n    if (await isApiTokenRevoked(apiTokenId, decoded, token, Date.now())) {\n      throw new TokenError('Token revoked')\n    }\n  }\n  return decoded\n}\n","sourceCodeStart":209,"sourceCodeEnd":232,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/token/src/token.ts#L209-L232","documentation":"verifyToken() rejects an otherwise valid, unexpired token because its apiTokenId (in decoded.extra) is reported as revoked by the registered apiTokenRevocationChecker. Revocation is checked with a short TTL cache; the account service is the sole authority, so a revoked token is refused even if its signature and expiry are still fine. Callers such as the transactor REST API and blob access surface this as a TokenError.","triggerScenarios":"verifyToken(token) is called; decodeToken succeeds, the token is not expired, decoded.extra.apiTokenId is defined, a revocation checker is registered (setApiTokenRevocationChecker), and the checker (or a fresh cache entry, i.e. outside REVOCATION_CACHE_TTL_MS) reports revoked=true.","commonSituations":"A user deleted or rotated their API token in the account service while an old token string is still configured in a client; an admin revoked a leaked token; a personal access token was removed after an offboarding.","solutions":["Obtain a fresh, non-revoked API token from the account service and replace the configured token.","Check the token's apiTokenId against the account service revocation list to confirm why it was revoked.","If the revocation was unintended, re-issue/restore the token in the account service.","If revocation should not apply, note that cache entries within the TTL are trusted — wait out the TTL after un-revoking, then retry."],"exampleFix":"// before\nconst token = process.env.OLD_API_TOKEN // revoked in account service\nawait verifyToken(token) // throws TokenError('Token revoked')\n// after\nconst token = process.env.API_TOKEN // freshly issued, unrevoked\nawait verifyToken(token)","handlingStrategy":"try-catch","validationCode":"import { decodeToken, isTokenExpired } from '<token-package>' // pseudo\nconst decoded = decodeToken(token, true, secret)\nif (isTokenExpired(decoded)) throw new Error('expired first')\nconst apiTokenId = decoded.extra?.apiTokenId\nif (apiTokenId !== undefined && !apiTokenId) throw new Error('malformed apiTokenId')","typeGuard":"function hasApiTokenId(t: unknown): t is Token & { extra: { apiTokenId: string } } {\n  return typeof t === 'object' && t !== null &&\n    typeof (t as any).extra?.apiTokenId === 'string'\n}","tryCatchPattern":"try {\n  const decoded = await verifyToken(token)\n  // use decoded\n} catch (e) {\n  if (e instanceof TokenError && e.message === 'Token revoked') {\n    // prompt re-auth / issue new API token\n  } else throw e\n}","preventionTips":["Refresh API tokens on a schedule rather than caching one indefinitely.","Treat any 4xx auth response as a prompt to re-fetch the token from the account service.","Monitor revocation events server-side and push token invalidation to clients proactively.","Never hard-code API tokens in config files; source them from a secret store you can rotate."],"tags":["auth","token-revocation","security"],"backgroundTag":"jwt-token-revoked","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}