{"record":{"id":"d25c00eaccab4be9","repo":"cloudflare/cloudflared","slug":"metadata-jwt-type-q-is-not-match","errorCode":null,"errorMessage":"metadata JWT type %q is not match","messagePattern":"metadata JWT type %q is not match","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":453,"sourceCode":"\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\n\treturn &AppInfo{\n\t\tAuthDomain:  authDomain.Hostname(),\n\t\tAppAUD:      claims.AUD,","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L435-L471","documentation":"GetAppInfo fetches a Cloudflare Access metadata JWT from the team domain and validates its claims before returning app info. After verifying the hostname, it checks that the JWT's `type` claim equals the expected value \"match\" (metadataMatchType). If the server returned a metadata JWT with any other type value, GetAppInfo rejects it with this error because the token does not certify that the requested host matches the Access application.","triggerScenarios":"Calling GetAppInfo (directly or via login, curl, generateToken, sshGen, createWebsocketStream) against a Cloudflare Access-protected domain when the cf-access-metadata response header contains a JWT whose `type` claim is not \"match\" — e.g. the URL does not correspond to a specific Access application, or the edge returned a differently-typed token.","commonSituations":"Requesting a domain protected by a wildcard/self-hosted Access app where the metadata endpoint answers with a non-matching token type; a Cloudflare-side change or misconfigured Access application; hitting a login page URL instead of the app's own hostname; proxy/CDN caching an unrelated metadata JWT.","solutions":["Verify the URL passed to GetAppInfo is the exact hostname of a Cloudflare Access application (not the team domain or a generic login URL).","Check the Access application configuration in the Cloudflare Zero Trust dashboard covers that hostname with an application of type self-hosted.","Clear any intermediate proxy/cache and retry so a fresh cf-access-metadata JWT is fetched.","Capture the raw JWT from the cf-access-metadata header (e.g. jwt.io) and inspect the `type` claim to confirm what the edge is returning.","Update cloudflared to the latest version in case the metadata type contract changed server-side."],"exampleFix":"// before\nappInfo, err := token.GetAppInfo(\"https://myteam.cloudflareaccess.com\", \"https://internal.example.com\")\n// after — ensure the app URL is the protected origin, not the login/team domain\nappInfo, err := token.GetAppInfo(\"https://myteam.cloudflareaccess.com\", \"https://app.internal.example.com\")","handlingStrategy":"validation","validationCode":"// fetch the raw metadata JWT and inspect the type claim before relying on GetAppInfo\nresp, err := http.Get(appURL)\nif err != nil { return err }\nraw := resp.Header.Get(\"cf-access-metadata\")\nif raw == \"\" { return errors.New(\"no metadata JWT; URL likely not behind Access\") }\nparts := strings.Split(raw, \".\")\nif len(parts) != 3 { return errors.New(\"malformed metadata JWT\") }\npayload, _ := base64.RawURLEncoding.DecodeString(parts[1])\nvar claims struct{ Type string `json:\"type\"` }\nif err := json.Unmarshal(payload, &claims); err != nil { return err }\nif claims.Type != \"match\" { return fmt.Errorf(\"unexpected metadata JWT type %q\", claims.Type) }","typeGuard":null,"tryCatchPattern":"appInfo, err := token.GetAppInfo(authDomain, appURL)\nif err != nil {\n    if strings.Contains(err.Error(), \"is not match\") {\n        // URL is not certified by Access metadata; surface a config hint\n        return fmt.Errorf(\"%w — verify %s is a Cloudflare Access application hostname\", err, appURL)\n    }\n    return err\n}","preventionTips":["Always pass the exact protected hostname, not the team domain or login URL.","Validate the app is configured as a self-hosted Access application before integrating.","Log the raw cf-access-metadata JWT when debugging metadata-type mismatches.","Pin cloudflared versions and test after Cloudflare Zero Trust policy changes."],"tags":["jwt","cloudflare-access","validation","auth"],"backgroundTag":"unexpected-response-shape","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"}