hcengineering/platform · warning · TokenError

Failed to verify token

Error message

Failed to verify token

What it means

decodeTokenVerbose wraps decodeToken and, when verification fails, attempts an unverified decode purely to log the token payload as a warning, then rethrows a TokenError with the underlying message. The 'Failed to verify token' log is diagnostic — the actual failure is the token signature/expiry/format problem inside decodeToken.

Source

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

 */
export function decodeToken (token: string, verify: boolean = true, secret?: string): Token {
  try {
    return decode(token, secret ?? getSecret(), !verify)
  } catch (err: any) {
    throw new TokenError(err.message)
  }
}

/**
 * @public
 */
export function decodeTokenVerbose (ctx: MeasureContext, token: string): Token {
  try {
    return decodeToken(token)
  } catch (err: any) {
    try {
      const decode = decodeToken(token, false)
      ctx.warn('Failed to verify token', { ...decode })
    } catch (err2: any) {
      // Nothing to do
    }
    throw new TokenError(err.message)
  }
}

/**
 * Checks whether a token has passed its `exp` (seconds since epoch) deadline.
 * `decodeToken` only verifies the signature — expiry must be checked separately.
 * @public
 */
export function isTokenExpired (token: Token, now: number = Date.now()): boolean {
  return token.exp !== undefined && token.exp * 1000 <= now
}

/**
 * Resolves whether a revokable API token (identified by `extra.apiTokenId`)

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Have the client obtain a fresh token (re-authenticate)
  2. Ensure the token signing secret/keys match between issuer and verifier
  3. Check system clocks / token expiry configuration
  4. Inspect the logged decode payload in the warning to see which claim failed

Example fix

// before
const token = staleTokenFromStorage
const data = decodeTokenVerbose(ctx, token) // throws TokenError
// after
let data
try {
  data = decodeTokenVerbose(ctx, staleTokenFromStorage)
} catch {
  data = decodeTokenVerbose(ctx, await requestNewToken())
}
Defensive patterns

Strategy: try-catch

Validate before calling

function isTokenUsable(token: string): boolean {
  return typeof token === 'string' && token.split('.').length === 3 && token.length > 20
}

Type guard

function isTokenError(e: unknown): e is TokenError {
  return e instanceof TokenError
}

Try / catch

try {
  data = decodeTokenVerbose(ctx, token)
} catch (err) {
  if (isTokenError(err)) {
    ctx.warn('token unusable, re-authenticating')
    token = await fetchNewToken()
    data = decodeTokenVerbose(ctx, token)
  } else throw err
}

Prevention

When it happens

Trigger: Calling decodeTokenVerbose with a token whose signature check fails, which is expired, or which is malformed and cannot even be decoded without verification.

Common situations: Expired sessions after token TTL passes; tokens signed with a rotated/changed JWT secret; clients sending garbage or truncated token strings; clock skew between issuer and verifier.

Related errors


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