{"record":{"id":"3220052275b975d9","repo":"PrefectHQ/fastmcp","slug":"invalid-redirect-uri","errorCode":"invalid_redirect_uri","errorMessage":"Redirect URI '{redirect_uri}' is not allowed.","messagePattern":"Redirect URI '(.+?)' is not allowed\\.","errorType":"error_code","errorClass":"RegistrationError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py","lineNumber":1014,"sourceCode":"        # *client record*: `OAuthClientMetadata.application_type` defaults to\n        # \"native\", while `OAuthClientInformationFull.application_type` is\n        # `str | None` and defaults to None. Normalize the unset case back to\n        # \"native\" so a client that omits the field gets the RFC 7591 default\n        # recorded explicitly, on both the HTTP and direct-call paths.\n        pending_application_type = _pending_application_type.get()\n        if pending_application_type is not None:\n            client_info.application_type = pending_application_type\n        elif client_info.application_type is None:\n            client_info.application_type = \"native\"\n        application_type = client_info.application_type\n\n        if client_info.redirect_uris:\n            for redirect_uri in client_info.redirect_uris:\n                if not validate_redirect_uri(\n                    redirect_uri=redirect_uri,\n                    allowed_patterns=self._allowed_client_redirect_uris,\n                ):\n                    raise RegistrationError(\n                        \"invalid_redirect_uri\",\n                        f\"Redirect URI '{redirect_uri}' is not allowed.\",\n                    )\n                # SEP-837: honor the client's declared application_type. \"web\"\n                # clients are restricted to non-loopback https redirect URIs.\n                if not is_redirect_uri_allowed_for_application_type(\n                    redirect_uri,\n                    application_type,\n                ):\n                    raise RegistrationError(\n                        \"invalid_redirect_uri\",\n                        f\"Redirect URI '{redirect_uri}' is not allowed for \"\n                        f\"application_type '{application_type}'.\",\n                    )\n        elif application_type == \"web\":\n            # Clients may omit redirect_uris and supply one at authorization,\n            # which falls back to the `http://localhost` placeholder below. A web\n            # client can never authorize against that placeholder (loopback http","sourceCodeStart":996,"sourceCodeEnd":1032,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py#L996-L1032","documentation":"During dynamic client registration, every redirect URI the client declares is checked against the proxy's configured allowed patterns (_allowed_client_redirect_uris). Any URI failing pattern validation raises RegistrationError with code invalid_redirect_uri, so the registration is rejected before the client is stored.","triggerScenarios":"POSTing to the DCR /register endpoint (or calling register_client/_start_flow directly) with redirect_uris entries that match none of the allowed patterns — e.g. http loopback URIs when only https patterns are configured.","commonSituations":"CLI or local dev clients registering http://localhost:port/callback against a production proxy that only allows https; a typo in the redirect path; proxy configured with restrictive patterns after upgrade.","solutions":["Register a redirect URI matching one of the configured allowed patterns","Adjust OAuthProxy's allowed client redirect URI pattern configuration to include the needed scheme/host","For local development, add a localhost loopback pattern explicitly"],"exampleFix":"// before\nPOST /register {\"redirect_uris\": [\"http://localhost:6274/oauth/callback\"]}  // https-only proxy\n// after\nPOST /register {\"redirect_uris\": [\"https://app.example.com/oauth/callback\"]}","handlingStrategy":"validation","validationCode":"import re\n\ndef uri_matches(uri: str, patterns: list[str]) -> bool:\n    return any(re.fullmatch(p.replace(\"*\", \".*\"), uri) for p in patterns)\n\npayload = {\"redirect_uris\": [\"https://app.example.com/cb\"], ...}\nassert all(uri_matches(u, ALLOWED_PATTERNS) for u in payload[\"redirect_uris\"]), \"URI not allowed\"","typeGuard":"def all_uris_allowed(uris: list[str] | None, patterns: list[str]) -> bool:\n    return bool(uris) and all(uri_matches(u, patterns) for u in uris)","tryCatchPattern":"try:\n    proxy.register_client(client_info)\nexcept RegistrationError as e:\n    if e.error == \"invalid_redirect_uri\":\n        return JSONResponse({\"error\": \"invalid_redirect_uri\", \"error_description\": str(e)}, status_code=400)\n    raise","preventionTips":["Verify each redirect URI against the proxy's allowed patterns before POSTing to /register","Use the same scheme/host/path the production proxy is configured for","For local dev, request a loopback pattern be added instead of guessing URIs"],"tags":["oauth","dcr","redirect-uri","registration"],"backgroundTag":"redirect-uri-mismatch","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}