cloudflare/cloudflared · error

metadata JWT auth_domain validation failed

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.

Solutions

  1. Decode the metadata JWT payload (base64 of the middle segment) and inspect the auth_domain claim to see the offending value.
  2. 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.
  3. Verify the team domain / auth domain configuration in the Zero Trust dashboard is a plain hostname.
  4. Bypass or fix any corporate proxy rewriting responses to the app URL.
  5. 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

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


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/127dc341d37a1bd8. Report an issue: GitHub.

Appendix: 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)