{"record":{"id":"1c91d8af5d992827","repo":"cube-js/cube","slug":"unable-to-verify-jwk-with-kid-decoded-header","errorCode":null,"errorMessage":"Unable to verify, JWK with kid: \"${decoded.header.kid}\" not found","messagePattern":"Unable to verify, JWK with kid: \"(.+?)\" not found","errorType":"http","errorClass":"CubejsHandlerError","httpStatus":403,"severity":"error","filePath":"packages/cubejs-api-gateway/src/gateway.ts","lineNumber":2691,"sourceCode":"            '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\n        return verifyToken(auth, jwk);\n      };\n    }\n\n    return async (req, auth) => {\n      if (auth) {\n        try {\n          req.securityContext = await checkAuthFn(auth);\n          req.signedWithPlaygroundAuthSecret =\n            Boolean(internalOptions?.isPlaygroundCheckAuth) && hasDevTokenScope(req.securityContext);\n        } catch (e: any) {\n          if (this.enforceSecurityChecks) {","sourceCodeStart":2673,"sourceCodeEnd":2709,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-api-gateway/src/gateway.ts#L2673-L2709","documentation":"With `jwkUrl` configured, Cube fetches the JWKS document and looks up the signing key whose `kid` matches the token's header `kid`. If no such key is found in the JWKS response, verification cannot proceed and this 403 Forbidden error is thrown. Note it is only thrown when the lookup returns falsy — e.g. key rotation has removed the old key.","triggerScenarios":"A JWT whose header `kid` (e.g. \"stale-key\") is absent from the JSON returned by the configured JWKS URL; or the JWKS URL itself returns an empty/incorrect key set (wrong tenant, wrong realm, partial page).","commonSituations":"IdP key rotation after which old tokens signed with a retired key are still presented; pointing jwkUrl at the wrong Auth0/Cognito/Keycloak endpoint; multi-tenant setups where jwkUrl() resolves to a different tenant's JWKS than the one that signed the token; JWKS caching holding an outdated key set.","solutions":["Verify the JWT's `kid` (decode the token header on jwt.io) and confirm a matching key exists at the configured jwkUrl.","Correct the jwkUrl (or the jwkUrl function's tenant resolution) to point to the JWKS endpoint of the IdP that actually signs your tokens.","Reissue client tokens after key rotation so they carry the current key's `kid`.","Check that the JWKS endpoint is reachable and returning x5c keys (Cube logs 'JWKs Background Fetching Error' when fetching fails).","Clear/restart to refresh cached JWKs if a newly published key has not been picked up."],"exampleFix":"// before — wrong JWKS URL (legacy endpoint)\njwt: { jwkUrl: 'https://app.example.com/.well-known/jwks.json' }\n// after — correct tenant JWKS\njwt: { jwkUrl: 'https://app.example.com/auth/realms/main/protocol/openid-connect/certs' }","handlingStrategy":"validation","validationCode":"async function kidExistsInJwks(token, jwkUrl) {\n  const { header } = jwt.decode(token, { complete: true });\n  const jwks = await (await fetch(jwkUrl)).json();\n  return jwks.keys.some(k => k.kid === header.kid);\n}\nif (!(await kidExistsInJwks(token, jwkUrl))) throw new Error(`kid not published in JWKS`);","typeGuard":"function jwksHasKid(jwks, kid) {\n  return Array.isArray(jwks?.keys) && jwks.keys.some(k => k.kid === kid);\n}","tryCatchPattern":"try {\n  await cubeApi.load(query);\n} catch (e) {\n  if (e.status === 403 && /JWK with kid/.test(e.message)) {\n    // refresh token from IdP / re-check jwkUrl tenant mapping\n  }\n  throw e;\n}","preventionTips":["Monitor IdP key-rotation events and reissue tokens on rotation","Prefetch and periodically diff your JWKS endpoint against token kids in health checks","Pin jwkUrl per tenant and add an integration test per tenant JWKS","Keep token lifetimes short so rotated-away keys age out quickly"],"tags":["auth","jwt","jwks","key-rotation","forbidden"],"backgroundTag":"jwk-kid-not-found","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}