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 e

View on GitHub (pinned to 1f02114297)

Solutions

  1. Call register_client with the client's metadata before attempting authorize, and reuse the exact client_id returned/stored
  2. If the provider was recreated, re-register the client (in-memory state is not persistent)
  3. 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

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

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/0a0809f1c0e3ae14. Report an issue: GitHub.