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

  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.