{"record":{"id":"127dc341d37a1bd8","repo":"cloudflare/cloudflared","slug":"metadata-jwt-auth-domain-validation-failed-w","errorCode":null,"errorMessage":"metadata JWT auth_domain validation failed: %w","messagePattern":"metadata JWT auth_domain validation failed: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":439,"sourceCode":"// against the account's public keys (fetched from the auth domain's JWKS\n// endpoint) to prevent an attacker-controlled server from spoofing app identity.\nfunc GetAppInfo(reqURL *url.URL) (*AppInfo, error) {\n\t// Fetch the metadata JWT from the edge (no redirects followed).\n\trawJWT, err := fetchMetadataJWT(reqURL.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Decode without verification to extract auth_domain for JWKS lookup.\n\tunverified, err := decodeMetadataUnverified(rawJWT)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Parse auth_domain into the canonical hostname used for JWKS lookup.\n\tauthDomain, err := parseAuthDomain(unverified.AuthDomain)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"metadata JWT auth_domain validation failed: %w\", err)\n\t}\n\n\t// Verify the JWT signature against the JWKS (with disk cache + retry).\n\tclaims, err := verifyMetadataWithRetry(rawJWT, authDomain)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"metadata JWT verification failed: %w\", err)\n\t}\n\n\t// Verify the hostname in the JWT matches the URL we actually requested.\n\tif !strings.EqualFold(claims.Hostname, reqURL.Hostname()) {\n\t\treturn nil, fmt.Errorf(\"metadata JWT hostname %q does not match request host %q\", claims.Hostname, reqURL.Hostname())\n\t}\n\tif claims.Type != metadataMatchType {\n\t\treturn nil, fmt.Errorf(\"metadata JWT type %q is not match\", claims.Type)\n\t}\n\tif claims.AUD == \"\" {\n\t\treturn nil, errors.New(\"metadata JWT aud is empty\")\n\t}","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L421-L457","documentation":"GetAppInfo wraps parseAuthDomain errors when the auth_domain claim decoded from the (unverified) metadata JWT cannot be parsed into a canonical hostname. The auth domain determines which JWKS endpoint is used to verify the JWT signature, so an invalid auth_domain means app identity cannot be trusted. The library throws this to reject forged or malformed metadata JWTs before any signature verification is attempted.","triggerScenarios":"Calling GetAppInfo (directly or via login/curl/generateToken/sshGen/createWebsocketStream) when the edge-returned metadata JWT carries an auth_domain that is empty, not a valid URL/host, or fails parseAuthDomain's canonicalization (e.g. contains a scheme+path the parser rejects).","commonSituations":"A reverse proxy or MITM device injecting a malformed Cf-Access-Jwt-Assertion response, non-Cloudflare servers replying to the metadata HEAD request with junk JWTs, custom team domains configured with unexpected formats, or older Access edge releases emitting legacy auth_domain values.","solutions":["Decode the metadata JWT payload (base64 of the middle segment) and inspect the auth_domain claim to see the offending value.","Confirm the request is actually reaching Cloudflare Access (curl -I the app URL and check for the metadata JWT header) rather than an intercepting proxy.","Verify the team domain / auth domain configuration in the Zero Trust dashboard is a plain hostname.","Bypass or fix any corporate proxy rewriting responses to the app URL.","Update cloudflared if the edge emits a newer auth_domain format than the local parser supports."],"exampleFix":"// before\ninfo, err := token.GetAppInfo(appURL) // fails: auth_domain validation failed\n// after: pre-check the URL and connectivity to the real Access edge\nif appURL.Hostname() == \"\" || strings.Contains(appURL.Hostname(), \"localhost\") {\n    return errors.New(\"app URL must point at the Access-protected public hostname\")\n}\ninfo, err := token.GetAppInfo(appURL)","handlingStrategy":"validation","validationCode":"if u, err := url.Parse(appURL.String()); err != nil || u.Hostname() == \"\" {\n    return errors.New(\"app URL must have a valid public hostname managed by Cloudflare Access\")\n}","typeGuard":"func isValidAppURL(u *url.URL) bool {\n    return u != nil && u.Scheme == \"https\" && u.Hostname() != \"\" &&\n        !strings.Contains(u.Hostname(), \"localhost\") &&\n        strings.Contains(u.Hostname(), \".\")\n}","tryCatchPattern":"info, err := token.GetAppInfo(appURL)\nif err != nil {\n    var valErr *fmt.Errorf\n    if errors.As(err, &valErr) && strings.Contains(err.Error(), \"auth_domain validation failed\") {\n        // likely proxy interception or non-Cloudflare response\n        return fmt.Errorf(\"check that %s is served by Cloudflare Access (no proxy rewriting): %w\", appURL.Host, err)\n    }\n    return err\n}","preventionTips":["Only pass HTTPS URLs of hostnames actually protected by Cloudflare Access.","Verify no corporate proxy rewrites the metadata response.","Confirm the team/auth domain in the Zero Trust dashboard is a plain hostname.","Decode the unverified JWT payload to inspect auth_domain when debugging."],"tags":["go","jwt","security","cloudflare-access","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}