{"record":{"id":"fb170f5d06d754da","repo":"PrefectHQ/fastmcp","slug":"invalid-target","errorCode":"invalid_target","errorMessage":"Resource does not match this server","messagePattern":"Resource does not match this server","errorType":"error_code","errorClass":"AuthorizeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py","lineNumber":1160,"sourceCode":"            server_url = str(self._resource_url)\n            client_url = str(client_resource)\n\n            if server_url_has_query(server_url):\n                # Server has query params - require exact match for security\n                urls_match = client_url.rstrip(\"/\") == server_url.rstrip(\"/\")\n            else:\n                # Server has no query params - normalize both for comparison\n                urls_match = normalize_resource_url(\n                    client_url\n                ) == normalize_resource_url(server_url)\n\n            if not urls_match:\n                logger.warning(\n                    \"Resource mismatch: client requested %s but server is %s\",\n                    client_resource,\n                    self._resource_url,\n                )\n                raise AuthorizeError(\n                    error=\"invalid_target\",  # type: ignore[arg-type]\n                    error_description=\"Resource does not match this server\",\n                )\n\n        # Generate transaction ID for this authorization request\n        txn_id = secrets.token_urlsafe(32)\n\n        # Generate proxy's own PKCE parameters if forwarding is enabled\n        proxy_code_verifier = None\n        proxy_code_challenge = None\n        if self._forward_pkce and params.code_challenge:\n            proxy_code_verifier, proxy_code_challenge = self._generate_pkce_pair()\n            logger.debug(\n                \"Generated proxy PKCE for transaction %s (forwarding client PKCE to upstream)\",\n                txn_id,\n            )\n\n        # Store transaction data for IdP callback processing","sourceCodeStart":1142,"sourceCodeEnd":1178,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py#L1142-L1178","documentation":"OAuth 2.0 resource indicator (RFC 8707) checking: when the authorization request includes a resource parameter, OAuthProxy compares it to the server's configured resource URL. A mismatch means the client is asking for tokens for a different audience, so authorize rejects the request with AuthorizeError code invalid_target.","triggerScenarios":"Calling authorize (via _start_flow, i.e. hitting the /authorize endpoint) with a resource query parameter that does not equal the configured _resource_url (e.g. wrong scheme, host, port, or path).","commonSituations":"Clients hardcoding an old or different server URL in the resource parameter; server deployed behind a proxy/domain different from what clients request; trailing-slash or path differences; clients that omit resource vs. those sending the wrong one.","solutions":["Set the client's resource parameter to the exact resource URL the server is configured with","Reconfigure the server's resource URL (base_url/resource parameter) to match what clients send","Normalize URL differences (scheme/host/port/trailing slash) on the client side"],"exampleFix":"// before\nGET /authorize?...&resource=https://old.example.com/mcp\n// after\nGET /authorize?...&resource=https://api.example.com/mcp  # matches server resource URL","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef resource_matches(requested: str, server_resource: str) -> bool:\n    a, b = urlparse(requested), urlparse(server_resource)\n    return (a.scheme, a.netloc, a.path.rstrip(\"/\")) == (b.scheme, b.netloc, b.path.rstrip(\"/\"))\n\nassert resource_matches(my_resource_param, SERVER_RESOURCE_URL)","typeGuard":"def has_valid_resource(params: dict, server: str) -> bool:\n    return \"resource\" not in params or resource_matches(params[\"resource\"], server)","tryCatchPattern":"# AuthorizeError surfaces as a redirect back to client with error=invalid_target\n\nasync def handle_authorize_error(e: AuthorizeError):\n    if e.error == \"invalid_target\":\n        logger.error(\"resource param does not match server resource URL: %s\", e.error_description)\n        return RedirectResponse(add_query(client_redirect_uri, {\"error\": \"invalid_target\"}))\n    raise","preventionTips":["Configure clients with the exact resource URL the proxy advertises","Re-check the resource parameter after changing domains, ports, or reverse-proxy paths","Prefer reading the resource URL from server metadata rather than hardcoding"],"tags":["oauth","rfc-8707","resource-indicator","audience"],"backgroundTag":"oauth-invalid-target","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}