{"record":{"id":"609467c6ce79c362","repo":"cloudflare/cloudflared","slug":"failed-to-parse-jwks","errorCode":null,"errorMessage":"failed to parse JWKS","messagePattern":"failed to parse JWKS","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/jwks.go","lineNumber":124,"sourceCode":"\t\treturn nil, errors.Wrapf(err, \"failed to fetch JWKS from %s\", jwksURL.String())\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"JWKS endpoint %s returned status %d\", jwksURL.String(), resp.StatusCode)\n\t}\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxJWKSResponseSize+1))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to read JWKS response body\")\n\t}\n\tif len(body) > maxJWKSResponseSize {\n\t\treturn nil, fmt.Errorf(\"JWKS response body exceeds %d bytes\", maxJWKSResponseSize)\n\t}\n\n\tvar keySet jose.JSONWebKeySet\n\tif err := json.Unmarshal(body, &keySet); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to parse JWKS\")\n\t}\n\treturn &keySet, nil\n}\n\n// jwksCachePath returns the on-disk path for cached JWKS for the given auth domain.\nfunc jwksCachePath(authDomain url.URL) (string, error) {\n\tconfigPath, err := getConfigPath()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tname := authDomain.Hostname() + jwksCacheSuffix\n\treturn filepath.Join(configPath, name), nil\n}\n\n// getCachedJWKS loads JWKS and its modification time from the disk cache.\n// A missing cache file is returned as a cache miss without an error.\nfunc getCachedJWKS(authDomain url.URL) (*jose.JSONWebKeySet, time.Time, error) {\n\tpath, err := jwksCachePath(authDomain)","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/jwks.go#L106-L142","documentation":"This error is returned by fetchJWKS when the JWKS endpoint returned HTTP 200 and a body within the size limit, but the body is not valid JSON or not shaped like a jose.JSONWebKeySet. json.Unmarshal failed, so the wrap preserves the JSON syntax error. It guards token verification against malformed or hijacked key-discovery responses.","triggerScenarios":"fetchJWKS receives a 200 response whose body cannot be unmarshaled into jose.JSONWebKeySet: HTML error pages served with 200, truncated JSON, wrong Content-Type served by a captive portal, or a proxy injecting content.","commonSituations":"Misconfigured auth domain pointing to a login page instead of the JWKS endpoint; captive portals on public Wi-Fi returning HTML with 200; load balancer serving a maintenance page; wrong path configured for the OIDC discovery/JWKS route.","solutions":["Verify the JWKS URL serves valid JSON: curl it and validate the body parses as JSON with a 'keys' array","Confirm the auth domain / JWKS path configuration is correct (no redirect to an HTML page)","Test connectivity without captive portal / proxy interference","If you control the IdP, check server logs for why a non-JWKS payload was returned"],"exampleFix":"// before: trusting any 200 body\nresp, _ := client.Get(jwksURL)\nkeySet, err := fetchJWKS(resp)\n// after: validating content-type before parsing\nresp, _ := client.Get(jwksURL)\nif ct := resp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n\treturn fmt.Errorf(\"unexpected JWKS content-type: %s\", ct)\n}\nkeySet, err := fetchJWKS(resp)","handlingStrategy":"validation","validationCode":"// verify the endpoint returns JSON before using it\nresp, err := http.Get(jwksURL)\nif err == nil {\n\tct := resp.Header.Get(\"Content-Type\")\n\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\n\tresp.Body.Close()\n\tif !strings.Contains(ct, \"json\") || !json.Valid(body) {\n\t\treturn fmt.Errorf(\"JWKS URL does not serve valid JSON (content-type %s)\", ct)\n\t}\n}","typeGuard":null,"tryCatchPattern":"keySet, err := fetchJWKS(url)\nif err != nil && strings.Contains(err.Error(), \"failed to parse JWKS\") {\n\t// log raw body for diagnosis; fail fast — retrying won't fix a bad URL\n\tlog.Error().Err(err).Str(\"url\", jwksURL).Msg(\"JWKS payload malformed; check auth domain config\")\n}","preventionTips":["Curl your JWKS URL and validate JSON output after every auth-domain change","Watch for captive-portal environments returning HTML with 200","Pin the exact /.well-known/jwks path in configuration","Add Content-Type checks upstream of parsing"],"tags":["json","jwks","parsing","oidc"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}