cube-js/cube · error · CubejsHandlerError
Unable to decode JWT key
Error message
Unable to decode JWT key
What it means
When Cube is configured to accept JWTs without public-key verification (decode-only checkAuth), it calls jwt.decode(auth, { complete: true }). If the Authorization token cannot be decoded as a JWT at all, the gateway responds 403 Forbidden with 'Unable to decode JWT key'. This means the token is not a syntactically valid JWT (header.payload.signature with base64 segments).
Source
Thrown at packages/cubejs-api-gateway/src/gateway.ts:2671
this.logger('JWKs Background Fetching Error', {
error: e.message,
});
},
});
this.releaseListeners.push(jwks.release);
// Precache JWKs response to speedup first auth
if (options.jwkUrl && typeof options.jwkUrl === 'string') {
jwks.fetchOnly(options.jwkUrl).catch((e) => this.logger('JWKs Prefetching Error', {
error: e.message,
}));
}
checkAuthFn = async (auth) => {
const decoded = <Record<string, any> | null>jwt.decode(auth, { complete: true });
if (!decoded) {
throw new CubejsHandlerError(
403,
'Forbidden',
'Unable to decode JWT key'
);
}
if (!decoded.header || !decoded.header.kid) {
throw new CubejsHandlerError(
403,
'Forbidden',
'JWT without kid inside headers'
);
}
const jwk = await jwks.getJWKbyKid(
typeof options.jwkUrl === 'function' ? await options.jwkUrl(decoded) : <string>options.jwkUrl,
decoded.header.kid
);View on GitHub (pinned to 7d981676b3)
Solutions
- Inspect the Authorization header and confirm it is a well-formed JWT (three dot-separated base64url segments); jwt.io can verify decode-ability.
- If your deployment uses shared secrets/tokens rather than JWTs, either issue a real JWT from your auth backend or change Cube's checkAuth configuration to match the token type.
- Fix token transport: send `Authorization: Bearer <jwt>` with no truncation, whitespace, or double-encoding; regenerate the token if it was corrupted.
- Verify the client SDK's token option receives the JWT (not an API key) — log the token before the request.
Example fix
// before
headers: { Authorization: 'my-secret-api-key' } // not a JWT, server does jwt.decode
// after
const token = jwt.sign({}, CUBEJS_SECRET, { expiresIn: '1d' });
headers: { Authorization: `Bearer ${token}` } Defensive patterns
Strategy: validation
Validate before calling
function looksLikeJwt(token) {
return typeof token === 'string' && token.trim().split('.').length === 3 && token.split('.').every(p => p.length > 0);
}
if (!looksLikeJwt(token)) throw new Error('Authorization value is not a decodable JWT'); Type guard
function isJwt(v: unknown): v is string {
return typeof v === 'string' && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/.test(v.trim());
} Try / catch
try {
return await api.request();
} catch (e) {
if (e?.status === 403 && String(e?.message).includes('Unable to decode JWT')) {
const fresh = await fetchNewJwt();
api.updateAuthorization(`Bearer ${fresh}`);
return await api.request();
}
throw e;
} Prevention
- Confirm server auth mode: JWT-based checkAuth requires actual JWTs, not shared-secret API keys.
- Send `Authorization: Bearer <jwt>` without truncation or stray whitespace.
- Validate token shape (three base64url segments) before attaching it to requests.
- Generate tokens with the same secret/config the Cube deployment expects (jwt.sign, not raw base64).
When it happens
Trigger: Request with an Authorization header whose value is not a decodable JWT: an opaque API token/secret pasted in place of a JWT, a token truncated or double-base64 encoded, an empty/whitespace token, or a session id.
Common situations: Conflicting auth setups — client sends a shared secret while the server expects a JWT (or vice versa after a config change); token generator producing raw base64 instead of proper JWT; tokens mangled by extra 'Bearer ' handling or whitespace/newlines; expired tokens that were stripped to an invalid fragment.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- JWT without kid inside headers
- Unable to verify, JWK with kid: "${decoded.header.kid}" not
- Invalid token
- e.toString()
- Authorization header isn't set
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/be4d57873f1985a3.
Report an issue: GitHub.