cloudflare/cloudflared · error
failed to parse JWKS
Error message
failed to parse JWKS
What it means
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.
Source
Thrown at token/jwks.go:124
return nil, errors.Wrapf(err, "failed to fetch JWKS from %s", jwksURL.String())
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("JWKS endpoint %s returned status %d", jwksURL.String(), resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, maxJWKSResponseSize+1))
if err != nil {
return nil, errors.Wrap(err, "failed to read JWKS response body")
}
if len(body) > maxJWKSResponseSize {
return nil, fmt.Errorf("JWKS response body exceeds %d bytes", maxJWKSResponseSize)
}
var keySet jose.JSONWebKeySet
if err := json.Unmarshal(body, &keySet); err != nil {
return nil, errors.Wrap(err, "failed to parse JWKS")
}
return &keySet, nil
}
// jwksCachePath returns the on-disk path for cached JWKS for the given auth domain.
func jwksCachePath(authDomain url.URL) (string, error) {
configPath, err := getConfigPath()
if err != nil {
return "", err
}
name := authDomain.Hostname() + jwksCacheSuffix
return filepath.Join(configPath, name), nil
}
// getCachedJWKS loads JWKS and its modification time from the disk cache.
// A missing cache file is returned as a cache miss without an error.
func getCachedJWKS(authDomain url.URL) (*jose.JSONWebKeySet, time.Time, error) {
path, err := jwksCachePath(authDomain)View on GitHub (pinned to 2253eeeb25)
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
Example fix
// before: trusting any 200 body
resp, _ := client.Get(jwksURL)
keySet, err := fetchJWKS(resp)
// after: validating content-type before parsing
resp, _ := client.Get(jwksURL)
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
return fmt.Errorf("unexpected JWKS content-type: %s", ct)
}
keySet, err := fetchJWKS(resp) Defensive patterns
Strategy: validation
Validate before calling
// verify the endpoint returns JSON before using it
resp, err := http.Get(jwksURL)
if err == nil {
ct := resp.Header.Get("Content-Type")
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if !strings.Contains(ct, "json") || !json.Valid(body) {
return fmt.Errorf("JWKS URL does not serve valid JSON (content-type %s)", ct)
}
} Try / catch
keySet, err := fetchJWKS(url)
if err != nil && strings.Contains(err.Error(), "failed to parse JWKS") {
// log raw body for diagnosis; fail fast — retrying won't fix a bad URL
log.Error().Err(err).Str("url", jwksURL).Msg("JWKS payload malformed; check auth domain config")
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/609467c6ce79c362.
Report an issue: GitHub.