{"record":{"id":"c3ef848c3a975599","repo":"cloudflare/cloudflared","slug":"metadata-jwt-aud-is-empty","errorCode":null,"errorMessage":"metadata JWT aud is empty","messagePattern":"metadata JWT aud is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":456,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"metadata JWT auth_domain validation failed: %w\", err)\n\t}\n\n\t// Verify the JWT signature against the JWKS (with disk cache + retry).\n\tclaims, err := verifyMetadataWithRetry(rawJWT, authDomain)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"metadata JWT verification failed: %w\", err)\n\t}\n\n\t// Verify the hostname in the JWT matches the URL we actually requested.\n\tif !strings.EqualFold(claims.Hostname, reqURL.Hostname()) {\n\t\treturn nil, fmt.Errorf(\"metadata JWT hostname %q does not match request host %q\", claims.Hostname, reqURL.Hostname())\n\t}\n\tif claims.Type != metadataMatchType {\n\t\treturn nil, fmt.Errorf(\"metadata JWT type %q is not match\", claims.Type)\n\t}\n\tif claims.AUD == \"\" {\n\t\treturn nil, errors.New(\"metadata JWT aud is empty\")\n\t}\n\tif err := validateMetadataIssuedAt(claims.IAT, time.Now()); err != nil {\n\t\treturn nil, err\n\t}\n\n\tappHostname := claims.AppHostname\n\tif appHostname == \"\" {\n\t\t// For retro-compatibility with CF access older releases, this will cause wildcard apps to store one local token\n\t\t// per requested hostname, which is less optimized but also works.\n\t\tappHostname = claims.Hostname\n\t}\n\n\treturn &AppInfo{\n\t\tAuthDomain:  authDomain.Hostname(),\n\t\tAppAUD:      claims.AUD,\n\t\tAppHostname: appHostname,\n\t}, nil\n}","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L438-L474","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the request carries the Access metadata JWT header populated by a successful Access login before calling GetAppInfo.","Configure the Access application's audience (AUD) tag in the Cloudflare dashboard so issued tokens include a non-empty aud claim.","Re-authenticate to regenerate a token if an old one predates the app's audience configuration.","Check intermediate proxies/load balancers are not stripping the metadata JWT header."],"exampleFix":"// before: request sent without Access token headers\n req, _ := http.NewRequestWithContext(ctx, \"GET\", originURL, nil)\n// after: inject metadata JWT from Access login\n req.Header.Set(cfAccessJWTHeader, metadataJWT)","handlingStrategy":"try-catch","validationCode":"claims, err := decodeUnverifiedClaims(metadataJWT) // base64-decode payload JSON\nif err != nil || claims.AUD == \"\" {\n    return fmt.Errorf(\"metadata JWT missing or has empty aud; re-run Access login\")\n}","typeGuard":"func hasAudience(claims *metadataClaims) bool {\n    return claims != nil && claims.AUD != \"\"\n}","tryCatchPattern":"appInfo, err := GetAppInfo(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"aud is empty\") {\n        // trigger re-authentication / token refresh flow\n        return reauthenticateAndRetry(ctx, req)\n    }\n    return fmt.Errorf(\"resolving app info: %w\", err)\n}","preventionTips":["Always run the Access login flow and attach its metadata JWT header before calling GetAppInfo.","Configure the app's audience tag in the Cloudflare dashboard so minted tokens carry a non-empty aud.","Check proxies for header stripping of the Access metadata JWT.","Refresh tokens after app/audience configuration changes."],"tags":["jwt","cloudflare-access","audience-claim","token"],"backgroundTag":"empty-required-field","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}