hcengineering/platform · error · TokenError
err.message
Error message
err.message
What it means
decodeToken wraps the underlying JWT decode/verify call; any failure (bad signature, malformed token, wrong secret) is rethrown as a TokenError carrying the underlying err.message. It is the library's single funnel for token decoding failures.
Source
Thrown at foundations/core/packages/token/src/token.ts:121
account: accountUuid,
workspace: workspaceUuid,
grant: sanitizedGrant,
sub,
exp,
nbf
},
secret ?? getSecret()
)
}
/**
* @public
*/
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)
}View on GitHub (pinned to 63e28dc964)
Solutions
- Ensure the same secret is configured on issuer and verifier (check the secret env var / metadata)
- Inspect the wrapped err.message — it names the underlying cause (e.g. signature mismatch, malformed JWT)
- Confirm the token is passed in full, without truncation or added whitespace/quotes
- Re-issue a fresh token if the token itself is corrupt or stale
Example fix
// before const t = decodeToken(authHeader) // 'Bearer eyJ...' including prefix -> malformed // after const raw = authHeader.replace(/^Bearer\s+/i, '') const t = decodeToken(raw)
Defensive patterns
Strategy: try-catch
Validate before calling
if (typeof token !== 'string' || token.split('.').length !== 3) {
throw new Error('token is not a well-formed JWT (expected 3 dot-separated parts)')
} Type guard
function looksLikeJwt(v: unknown): v is string {
return typeof v === 'string' && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/.test(v)
} Try / catch
try {
const decoded = decodeToken(token, true)
} catch (e) {
if (e instanceof TokenError) {
// e.message carries the underlying cause: signature, malformed, etc.
throw new UnauthorizedError(`bad token: ${e.message}`)
}
throw e
} Prevention
- Strip 'Bearer ' prefixes and surrounding quotes before decoding
- Keep JWT secrets identical across all services and environments (shared config)
- Log token prefixes (never full tokens) to diagnose truncation
- Use decodeTokenVerbose to get the decoded payload in warnings when verification fails
When it happens
Trigger: Calling decodeToken with a token that is truncated, tampered, signed with a different secret, or not a JWT at all; or with verify=true and a mismatched secret (getSecret() result differing from the signing secret).
Common situations: Secrets differing across services/environments (JWT_SECRET env mismatch), tokens from another deployment, expired/corrupted cookies or Authorization headers, copies missing trailing characters.
Related errors
- Workspace or account not found in token
- nbf and exp are required when sub is not provided
- Token expired
- Missing account in token
- Missing workspace in token
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/54dc1bde1ae06885.
Report an issue: GitHub.