{"record":{"id":"8791c008cd622a32","repo":"cube-js/cube","slug":"jwt-without-kid-inside-headers","errorCode":null,"errorMessage":"JWT without kid inside headers","messagePattern":"JWT without kid inside headers","errorType":"http","errorClass":"CubejsHandlerError","httpStatus":403,"severity":"error","filePath":"packages/cubejs-api-gateway/src/gateway.ts","lineNumber":2679,"sourceCode":"      // Precache JWKs response to speedup first auth\n      if (options.jwkUrl && typeof options.jwkUrl === 'string') {\n        jwks.fetchOnly(options.jwkUrl).catch((e) => this.logger('JWKs Prefetching Error', {\n          error: e.message,\n        }));\n      }\n\n      checkAuthFn = async (auth) => {\n        const decoded = <Record<string, any> | null>jwt.decode(auth, { complete: true });\n        if (!decoded) {\n          throw new CubejsHandlerError(\n            403,\n            'Forbidden',\n            'Unable to decode JWT key'\n          );\n        }\n\n        if (!decoded.header || !decoded.header.kid) {\n          throw new CubejsHandlerError(\n            403,\n            'Forbidden',\n            'JWT without kid inside headers'\n          );\n        }\n\n        const jwk = await jwks.getJWKbyKid(\n          typeof options.jwkUrl === 'function' ? await options.jwkUrl(decoded) : <string>options.jwkUrl,\n          decoded.header.kid\n        );\n        if (!jwk) {\n          throw new CubejsHandlerError(\n            403,\n            'Forbidden',\n            `Unable to verify, JWK with kid: \"${decoded.header.kid}\" not found`\n          );\n        }\n","sourceCodeStart":2661,"sourceCodeEnd":2697,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-api-gateway/src/gateway.ts#L2661-L2697","documentation":"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.","triggerScenarios":"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 }`.","commonSituations":"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.","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."],"exampleFix":"// before\nconst token = jwt.sign(payload, privateKey);\n// after\nconst token = jwt.sign(payload, privateKey, { keyid: 'cube-key-1' });","handlingStrategy":"validation","validationCode":"function hasKidHeader(token) {\n  const decoded = jwt.decode(token, { complete: true });\n  return Boolean(decoded && decoded.header && decoded.header.kid);\n}\nif (!hasKidHeader(myToken)) throw new Error('Token lacks kid header; reissue with keyid');","typeGuard":"function isDecodedJwtWithKid(v: unknown): v is { header: { kid: string; alg: string }, payload: object } {\n  const d = v as any;\n  return !!d && typeof d === 'object' && !!d.header && typeof d.header.kid === 'string' && d.header.kid.length > 0;\n}","tryCatchPattern":"try {\n  await cubeApi.load(query);\n} catch (e) {\n  if (e.status === 403 && /kid/i.test(e.message)) {\n    // reissue token with keyid header before retrying\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["auth","jwt","jwks","forbidden"],"backgroundTag":"jwt-missing-kid-header","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}