cube-js/cube · error · CubejsHandlerError
JWT without kid inside headers
Error message
JWT without kid inside headers
What it means
When JWKS-based auth is configured (jwkUrl option), Cube decodes the incoming JWT and requires the token header to contain a `kid` (key ID) so it can select the matching signing key from the JWKS endpoint. If the token decodes but its header has no `kid`, the gateway throws this 403 Forbidden error because it cannot determine which key to verify against.
Source
Thrown at packages/cubejs-api-gateway/src/gateway.ts:2679
// 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
);
if (!jwk) {
throw new CubejsHandlerError(
403,
'Forbidden',
`Unable to verify, JWK with kid: "${decoded.header.kid}" not found`
);
}
View on GitHub (pinned to 7d981676b3)
Solutions
- Configure your identity provider / token issuer to include the `kid` header in issued JWTs (e.g. in jsonwebtoken: jwt.sign(payload, key, { keyid: 'my-key-id' })).
- Ensure the JWKS endpoint publishes the key whose `kid` matches the one your tokens carry.
- If you don't use JWKS, remove the jwkUrl option and use a static `key` or a custom `checkAuth` function instead.
- Regenerate/reissue client tokens after fixing the issuer so old kid-less tokens are not in circulation.
Example fix
// before
const token = jwt.sign(payload, privateKey);
// after
const token = jwt.sign(payload, privateKey, { keyid: 'cube-key-1' }); Defensive patterns
Strategy: validation
Validate before calling
function hasKidHeader(token) {
const decoded = jwt.decode(token, { complete: true });
return Boolean(decoded && decoded.header && decoded.header.kid);
}
if (!hasKidHeader(myToken)) throw new Error('Token lacks kid header; reissue with keyid'); Type guard
function isDecodedJwtWithKid(v: unknown): v is { header: { kid: string; alg: string }, payload: object } {
const d = v as any;
return !!d && typeof d === 'object' && !!d.header && typeof d.header.kid === 'string' && d.header.kid.length > 0;
} Try / catch
try {
await cubeApi.load(query);
} catch (e) {
if (e.status === 403 && /kid/i.test(e.message)) {
// reissue token with keyid header before retrying
}
throw e;
} Prevention
- Always sign tokens with a keyid option when using JWKS-based auth
- Decode token headers in CI tests to assert kid presence before deploying
- Document the kid requirement for any internal service minting tokens
When it happens
Trigger: A request with an Authorization header whose JWT was signed without a `kid` in its JOSE header — e.g. tokens minted by a library that omits kid when only one key exists — while the Cube instance is configured with `jwt: { jwkUrl }`.
Common situations: Switching an existing Cube deployment from symmetric-secret (key/checkAuth) auth to JWKS auth while clients still present old tokens; an identity provider configured to not emit kid; hand-rolled token generation in tests or scripts using jsonwebtoken's sign() without keyid.
Related errors
- Unable to verify, JWK with kid: "${decoded.header.kid}" not
- Invalid token
- Unable to decode JWT key
- Authorization header isn't set
- API scope is missing: ${scope}
AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02).
Data as JSON: /api/errors/8791c008cd622a32.
Report an issue: GitHub.