cloudflare/cloudflared · error

auth_domain %q does not end with %q

Error message

auth_domain %q does not end with %q

What it means

After parsing, parseAuthDomain lowercases the hostname and enforces that the auth_domain ends with the Cloudflare Access suffix ".cloudflareaccess.com". This is a security check: JWKS requests and cache paths must only ever target Cloudflare Access domains, preventing a tampered metadata JWT from redirecting token verification to an attacker-controlled host. A non-matching auth_domain is rejected with "auth_domain %q does not end with %q".

Source

Thrown at token/jwks.go:88

	}

	var claims metadataClaims
	if err := json.Unmarshal(payload, &claims); err != nil {
		return nil, errors.Wrap(err, "failed to decode verified metadata JWT claims")
	}
	return &claims, nil
}

// parseAuthDomain extracts the canonical hostname used for JWKS requests and
// cache paths from the auth_domain claim.
func parseAuthDomain(authDomain string) (url.URL, error) {
	parsed, err := url.Parse(httpsScheme + "://" + authDomain)
	if err != nil {
		return url.URL{}, fmt.Errorf("failed to parse auth_domain %q: %w", authDomain, err)
	}
	hostname := strings.ToLower(parsed.Hostname())
	if !strings.HasSuffix(hostname, accessDomainSuffix) {
		return url.URL{}, fmt.Errorf("auth_domain %q does not end with %q", authDomain, accessDomainSuffix)
	}
	return url.URL{Scheme: httpsScheme, Host: hostname}, nil
}

// fetchJWKS fetches the JWKS from the auth domain's certs endpoint over HTTPS.
func fetchJWKS(authDomain url.URL) (*jose.JSONWebKeySet, error) {
	jwksURL := authDomain
	jwksURL.Path = accessCertPath

	client := &http.Client{
		CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
			return http.ErrUseLastResponse
		},
		Timeout: time.Second * 10,
	}
	resp, err := client.Get(jwksURL.String()) // nolint: gosec
	if err != nil {
		return nil, errors.Wrapf(err, "failed to fetch JWKS from %s", jwksURL.String())

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Use the standard Cloudflare Access team domain ending in .cloudflareaccess.com for auth_domain (find it in the Zero Trust dashboard under Access).
  2. Re-issue/re-download the tunnel token from the Cloudflare dashboard — a stale or hand-edited token may carry a wrong auth_domain.
  3. If the JWT came from an untrusted source, treat this as a possible tampering attempt and fetch metadata only from Cloudflare edge.
  4. If you legitimately need a custom auth domain, this library does not support it; do not bypass the suffix check — keep the standard domain for JWKS verification.

Example fix

// before
"auth_domain": "myteam.example.com"
// after
"auth_domain": "myteam.cloudflareaccess.com"
Defensive patterns

Strategy: validation

Validate before calling

func isCloudflareAccessDomain(authDomain string) bool {
	u, err := url.Parse("https://" + authDomain)
	if err != nil {
		return false
	}
	return strings.HasSuffix(strings.ToLower(u.Hostname()), ".cloudflareaccess.com")
}

Type guard

func isAccessAuthDomain(u url.URL) bool {
	return u.Scheme == "https" && strings.HasSuffix(u.Host, ".cloudflareaccess.com")
}

Try / catch

ad, err := parseAuthDomain(claims.AuthDomain)
if err != nil {
	if strings.Contains(err.Error(), "does not end with") {
		log.Error().Str("authDomain", claims.AuthDomain).
			Msg("auth_domain is not a Cloudflare Access domain; possible tampering or misconfiguration — refusing JWKS fetch")
	}
	return err
}

Prevention

When it happens

Trigger: GetAppInfo / testAuthDomain / metadata processing receive an auth_domain claim whose hostname does not end in ".cloudflareaccess.com" — e.g. "myteam.example.com", "myteam.cloudflareaccess.com.evil.io", an empty string, or a claim missing entirely so the value is blank.

Common situations: A self-hosted or third-party Access-like setup whose domain is not on cloudflareaccess.com is plugged into cloudflared; the metadata JWT was tampered with or generated by a mock/stub in a custom environment; the team domain in the dashboard was customized and doesn't use the standard Access suffix; a typo in a hand-edited tunnel token.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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