cloudflare/cloudflared · error
metadata JWT auth_domain validation failed: %w
Error message
metadata JWT auth_domain validation failed: %w
What it means
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.
Source
Thrown at token/token.go:439
// against the account's public keys (fetched from the auth domain's JWKS
// endpoint) to prevent an attacker-controlled server from spoofing app identity.
func GetAppInfo(reqURL *url.URL) (*AppInfo, error) {
// Fetch the metadata JWT from the edge (no redirects followed).
rawJWT, err := fetchMetadataJWT(reqURL.String())
if err != nil {
return nil, err
}
// Decode without verification to extract auth_domain for JWKS lookup.
unverified, err := decodeMetadataUnverified(rawJWT)
if err != nil {
return nil, err
}
// Parse auth_domain into the canonical hostname used for JWKS lookup.
authDomain, err := parseAuthDomain(unverified.AuthDomain)
if err != nil {
return nil, fmt.Errorf("metadata JWT auth_domain validation failed: %w", err)
}
// Verify the JWT signature against the JWKS (with disk cache + retry).
claims, err := verifyMetadataWithRetry(rawJWT, authDomain)
if err != nil {
return nil, fmt.Errorf("metadata JWT verification failed: %w", err)
}
// Verify the hostname in the JWT matches the URL we actually requested.
if !strings.EqualFold(claims.Hostname, reqURL.Hostname()) {
return nil, fmt.Errorf("metadata JWT hostname %q does not match request host %q", claims.Hostname, reqURL.Hostname())
}
if claims.Type != metadataMatchType {
return nil, fmt.Errorf("metadata JWT type %q is not match", claims.Type)
}
if claims.AUD == "" {
return nil, errors.New("metadata JWT aud is empty")
}View on GitHub (pinned to 2253eeeb25)
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.
Example fix
// before
info, err := token.GetAppInfo(appURL) // fails: auth_domain validation failed
// after: pre-check the URL and connectivity to the real Access edge
if appURL.Hostname() == "" || strings.Contains(appURL.Hostname(), "localhost") {
return errors.New("app URL must point at the Access-protected public hostname")
}
info, err := token.GetAppInfo(appURL) Defensive patterns
Strategy: validation
Validate before calling
if u, err := url.Parse(appURL.String()); err != nil || u.Hostname() == "" {
return errors.New("app URL must have a valid public hostname managed by Cloudflare Access")
} Type guard
func isValidAppURL(u *url.URL) bool {
return u != nil && u.Scheme == "https" && u.Hostname() != "" &&
!strings.Contains(u.Hostname(), "localhost") &&
strings.Contains(u.Hostname(), ".")
} Try / catch
info, err := token.GetAppInfo(appURL)
if err != nil {
var valErr *fmt.Errorf
if errors.As(err, &valErr) && strings.Contains(err.Error(), "auth_domain validation failed") {
// likely proxy interception or non-Cloudflare response
return fmt.Errorf("check that %s is served by Cloudflare Access (no proxy rewriting): %w", appURL.Host, err)
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- metadata JWT verification failed: %w
- metadata JWT hostname %q does not match request host %q
- auth_domain %q does not end with %q
- metadata JWT type %q is not match
- invalid token
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/127dc341d37a1bd8.
Report an issue: GitHub.