PrefectHQ/fastmcp · error · AuthorizeError
unauthorized_client
unauthorized_client
Error message
unauthorized_client: Client '{client.client_id}' not registered. What it means
During the simulated authorization step, the in-memory provider looks up the client_id in its registered clients dict. If the client was never registered (or was registered with a different id), it raises an OAuth AuthorizeError with error code 'unauthorized_client' per RFC 6749 §4.1.2.1.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/providers/in_memory.py:105
if client_info.client_id is None:
raise ValueError("client_id is required for client registration")
if client_info.client_id in self.clients:
# As per RFC 7591, if client_id is already known, it's an update.
# For this simple provider, we'll treat it as re-registration.
# A real provider might handle updates or raise errors for conflicts.
pass
self.clients[client_info.client_id] = client_info
async def authorize(
self, client: OAuthClientInformationFull, params: AuthorizationParams
) -> str:
"""
Simulates user authorization and generates an authorization code.
Returns a redirect URI with the code and state.
"""
if client.client_id not in self.clients:
raise AuthorizeError(
error="unauthorized_client",
error_description=f"Client '{client.client_id}' not registered.",
)
# Validate redirect_uri (already validated by AuthorizationHandler, but good practice)
try:
# OAuthClientInformationFull should have a method like validate_redirect_uri
# For this test provider, we assume it's valid if it matches one in client_info
# The AuthorizationHandler already does robust validation using client.validate_redirect_uri
if client.redirect_uris and params.redirect_uri not in client.redirect_uris:
# This check might be too simplistic if redirect_uris can be patterns
# or if params.redirect_uri is None and client has a default.
# However, the AuthorizationHandler handles the primary validation.
pass # Let's assume AuthorizationHandler did its job.
except Exception as e: # Replace with specific validation error if client.validate_redirect_uri existed
raise AuthorizeError(
error="invalid_request", error_description="Invalid redirect_uri."
) from eView on GitHub (pinned to 1f02114297)
Solutions
- Call register_client with the client's metadata before attempting authorize, and reuse the exact client_id returned/stored
- If the provider was recreated, re-register the client (in-memory state is not persistent)
- Log client.client_id and provider.clients keys to find id mismatches
Example fix
// before client = Client(client_id="app-1", ...) redirect = provider.authorize(client, params) # 'app-1' never registered // after provider.register_client(OAuthClientMetadata(client_id="app-1", redirect_uris=[...])) redirect = provider.authorize(client, params)
Defensive patterns
Strategy: validation
Validate before calling
if client.client_id not in provider.clients:
provider.register_client(client_metadata) # re-register before authorize
redirect = provider.authorize(client, params) Type guard
def is_registered(provider, client) -> bool:
return client.client_id is not None and client.client_id in provider.clients Try / catch
try:
redirect = provider.authorize(client, params)
except AuthorizeError as e:
if e.error == "unauthorized_client":
# re-register and retry once
provider.register_client(client_metadata)
redirect = provider.authorize(client, params) Prevention
- Register clients in the same process/session that performs authorization
- Reuse the exact client_id returned by registration; avoid stringly-typed copies
- Remember in-memory provider state is lost on restart — persist or re-register
When it happens
Trigger: Calling authorize(client, params) with a Client object whose client_id was never passed to register_client, or after provider state was reset (new InMemoryOAuthProvider instance) while the client kept its old id.
Common situations: Server restarted between registration and authorization (in-memory state lost); client_id typo or case mismatch; registering with one provider instance but authorizing against another.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- invalid_request
- invalid_client
- OAuth authorization failed: {error} - {error_desc}
- The device authorization request expired
- The device authorization request was denied
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/0a0809f1c0e3ae14.
Report an issue: GitHub.