{"record":{"id":"be4d57873f1985a3","repo":"cube-js/cube","slug":"unable-to-decode-jwt-key","errorCode":null,"errorMessage":"Unable to decode JWT key","messagePattern":"Unable to decode JWT key","errorType":"http","errorClass":"CubejsHandlerError","httpStatus":403,"severity":"error","filePath":"packages/cubejs-api-gateway/src/gateway.ts","lineNumber":2671,"sourceCode":"          this.logger('JWKs Background Fetching Error', {\n            error: e.message,\n          });\n        },\n      });\n\n      this.releaseListeners.push(jwks.release);\n\n      // 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        );","sourceCodeStart":2653,"sourceCodeEnd":2689,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-api-gateway/src/gateway.ts#L2653-L2689","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nheaders: { Authorization: 'my-secret-api-key' } // not a JWT, server does jwt.decode\n// after\nconst token = jwt.sign({}, CUBEJS_SECRET, { expiresIn: '1d' });\nheaders: { Authorization: `Bearer ${token}` }","handlingStrategy":"validation","validationCode":"function looksLikeJwt(token) {\n  return typeof token === 'string' && token.trim().split('.').length === 3 && token.split('.').every(p => p.length > 0);\n}\nif (!looksLikeJwt(token)) throw new Error('Authorization value is not a decodable JWT');","typeGuard":"function isJwt(v: unknown): v is string {\n  return typeof v === 'string' && /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*$/.test(v.trim());\n}","tryCatchPattern":"try {\n  return await api.request();\n} catch (e) {\n  if (e?.status === 403 && String(e?.message).includes('Unable to decode JWT')) {\n    const fresh = await fetchNewJwt();\n    api.updateAuthorization(`Bearer ${fresh}`);\n    return await api.request();\n  }\n  throw e;\n}","preventionTips":["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)."],"tags":["auth","jwt","http-403"],"backgroundTag":"jwt-decode-failed","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}