{"record":{"id":"ad8acbc939ff5ce2","repo":"cloudflare/cloudflared","slug":"metadata-jwt-hostname-q-does-not-match-request-ho","errorCode":null,"errorMessage":"metadata JWT hostname %q does not match request host %q","messagePattern":"metadata JWT hostname %q does not match request host %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":450,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Parse auth_domain into the canonical hostname used for JWKS lookup.\n\tauthDomain, err := parseAuthDomain(unverified.AuthDomain)\n\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","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L432-L468","documentation":"GetAppInfo rejects the metadata JWT when its Hostname claim does not case-insensitively match the hostname of the URL that was actually requested. This anti-spoofing check ensures the JWT describes the exact app you contacted; a mismatch means the token was issued for a different hostname (or an attacker is replaying a JWT across apps).","triggerScenarios":"Calling GetAppInfo with a reqURL whose hostname differs from the JWT's hostname claim — e.g. the HEAD request followed an implicit host rewrite, the URL was built with a different case/alias domain, a proxy answered on behalf of another vhost, or the JWT was captured from a different Access application.","commonSituations":"CNAME/vhost misconfiguration where one origin serves multiple Access apps, using an internal alias (e.g. app.internal) that maps to a different public hostname, stale token-replay from a sibling app, wildcard Access apps returning a different AppHostname, or hand-built URLs during testing.","solutions":["Print both hostnames from the error and correct the reqURL to use the exact public hostname of the Access app.","Check the Access application's configured domain in the Zero Trust dashboard matches the URL you request.","Remove hosts-file/DNS overrides or reverse-proxy rules that rewrite the requested Host header.","If using a wildcard app, request the concrete hostname and rely on AppHostname rather than reusing JWTs across hostnames.","Ensure the JWT was freshly fetched for this URL (no cached/replayed metadata JWT from another app)."],"exampleFix":"// before: requesting via internal alias, JWT hostname mismatches\nappURL, _ := url.Parse(\"https://app.internal/dashboard\")\ninfo, err := token.GetAppInfo(appURL) // hostname \"app.internal\" != \"app.example.com\"\n// after: use the public Access hostname\nappURL, _ := url.Parse(\"https://app.example.com/dashboard\")\ninfo, err := token.GetAppInfo(appURL)","handlingStrategy":"validation","validationCode":"publicHost := \"app.example.com\" // exact hostname configured in the Access application\nif appURL.Hostname() != publicHost {\n    return fmt.Errorf(\"must request the Access app's public hostname %s, got %s\", publicHost, appURL.Hostname())\n}","typeGuard":"func hostnameMatches(appURL *url.URL, accessAppHostname string) bool {\n    return appURL != nil && strings.EqualFold(appURL.Hostname(), accessAppHostname)\n}","tryCatchPattern":"info, err := token.GetAppInfo(appURL)\nif err != nil {\n    if strings.Contains(err.Error(), \"does not match request host\") {\n        return fmt.Errorf(\"URL hostname does not match the Access application; use the exact public hostname configured in Zero Trust: %w\", err)\n    }\n    return err\n}","preventionTips":["Always request the exact public hostname configured on the Access application, not internal aliases or CNAMEs.","Compare the error's two quoted hostnames to spot DNS/hosts overrides or proxy Host rewrites.","Avoid caching or replaying metadata JWTs across different app hostnames.","For wildcard apps, request concrete hostnames and expect AppHostname to carry the wildcard base."],"tags":["go","jwt","security","hostname-mismatch","cloudflare-access"],"backgroundTag":"jwt-hostname-mismatch","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}