{"record":{"id":"47255c65bd3075f9","repo":"hcengineering/platform","slug":"token-expired","errorCode":null,"errorMessage":"Token expired","messagePattern":"Token expired","errorType":"exception","errorClass":"TokenError","httpStatus":null,"severity":"error","filePath":"foundations/core/packages/token/src/token.ts","lineNumber":222,"sourceCode":"    // do not know whether this token still stands, so refuse it rather than let a\n    // revoked token survive by making the account unreachable. A verdict from\n    // within the TTL is still trusted, which keeps brief outages from cutting off\n    // healthy tokens mid-flight.\n    throw new TokenError('Token revocation could not be verified')\n  }\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":204,"sourceCodeEnd":232,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/token/src/token.ts#L204-L232","documentation":"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').","triggerScenarios":"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.","commonSituations":"Long-lived client sessions using stale tokens, clocks skewing between issuer and consumer, cached tokens past their expiry, forgetting to refresh tokens before requests.","solutions":["Obtain a fresh token (re-authenticate or use a refresh flow)","Check expiry client-side via isTokenExpired before sending requests and refresh proactively","Verify system clocks are synchronized (NTP) on issuer and verifier","Issue longer-lived tokens or implement refresh tokens if expiry is too short"],"exampleFix":"// before\nconst token = cachedToken\nconst t = await verifyToken(token) // may be expired\n// after\nconst token = cachedToken\nif (isTokenExpired(decodeToken(token, false))) {\n  cachedToken = await refreshToken()\n}\nconst t = await verifyToken(cachedToken)","handlingStrategy":"validation","validationCode":"const decoded = decodeToken(token, false) // signature still checked with verify=true in real flow\nif (isTokenExpired(decoded)) {\n  await refreshToken()\n}","typeGuard":"function isTokenUsable(t: Token): boolean {\n  return !isTokenExpired(t)\n}","tryCatchPattern":"try {\n  const t = await verifyToken(token)\n} catch (e) {\n  if (e instanceof TokenError && e.message === 'Token expired') {\n    await refreshSession()\n    return verifyToken(newToken)\n  }\n  throw e\n}","preventionTips":["Check exp client-side and refresh proactively before requests","Implement refresh-token flows instead of re-prompting login on expiry","Synchronize clocks (NTP) across services to avoid premature/late expiry","Distinguish 'Token expired' (refresh) from 'Token revoked' (re-authenticate) in handlers"],"tags":["jwt","token","expiry"],"backgroundTag":"jwt-token-expired","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}