cloudflare/cloudflared · error

metadata JWT aud is empty

Error message

metadata JWT aud is empty

What it means

GetAppInfo validates the cloudflared access metadata JWT that must be attached to a request before it can resolve app information. After verifying the signature, hostname and type, it rejects the token if the AUD claim is empty, since the audience is required to identify the Access application.

Source

Thrown at token/token.go:456

	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")
	}
	if err := validateMetadataIssuedAt(claims.IAT, time.Now()); err != nil {
		return nil, err
	}

	appHostname := claims.AppHostname
	if appHostname == "" {
		// For retro-compatibility with CF access older releases, this will cause wildcard apps to store one local token
		// per requested hostname, which is less optimized but also works.
		appHostname = claims.Hostname
	}

	return &AppInfo{
		AuthDomain:  authDomain.Hostname(),
		AppAUD:      claims.AUD,
		AppHostname: appHostname,
	}, nil
}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Ensure the request carries the Access metadata JWT header populated by a successful Access login before calling GetAppInfo.
  2. Configure the Access application's audience (AUD) tag in the Cloudflare dashboard so issued tokens include a non-empty aud claim.
  3. Re-authenticate to regenerate a token if an old one predates the app's audience configuration.
  4. Check intermediate proxies/load balancers are not stripping the metadata JWT header.

Example fix

// before: request sent without Access token headers
 req, _ := http.NewRequestWithContext(ctx, "GET", originURL, nil)
// after: inject metadata JWT from Access login
 req.Header.Set(cfAccessJWTHeader, metadataJWT)
Defensive patterns

Strategy: try-catch

Validate before calling

claims, err := decodeUnverifiedClaims(metadataJWT) // base64-decode payload JSON
if err != nil || claims.AUD == "" {
    return fmt.Errorf("metadata JWT missing or has empty aud; re-run Access login")
}

Type guard

func hasAudience(claims *metadataClaims) bool {
    return claims != nil && claims.AUD != ""
}

Try / catch

appInfo, err := GetAppInfo(ctx, req)
if err != nil {
    if strings.Contains(err.Error(), "aud is empty") {
        // trigger re-authentication / token refresh flow
        return reauthenticateAndRetry(ctx, req)
    }
    return fmt.Errorf("resolving app info: %w", err)
}

Prevention

When it happens

Trigger: Calling GetAppInfo with a request whose metadata JWT has an empty or missing aud claim; also hit by tests like TestGetAppInfo_RejectsNoMetadataHeader exercising the no/empty metadata path via createWebsocketStream, login, curl, generateToken, or sshGen.

Common situations: Requests to an Access-protected origin that bypass the Access login flow (no token injected), tokens minted without an audience configured for the app in the Cloudflare dashboard, or a header-stripping proxy removing the metadata JWT or its aud.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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