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
- 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
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
- 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
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- nbf and exp are required when sub is not provided
- Workspace or account not found in token
- err.message
- Missing account in token
- Missing workspace in token
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/47255c65bd3075f9.
Report an issue: GitHub.