PrefectHQ/fastmcp · error · RuntimeError

OAuth authorization failed: {error} - {error_desc}

Error message

OAuth authorization failed: {error} - {error_desc}

What it means

When the OAuth provider redirects back with an `error` query parameter instead of a `code`, callback_handler raises RuntimeError containing the error code and description from the provider (e.g. access_denied, invalid_client). This surfaces authorization failures that happened on the provider side.

Source

Thrown at fastmcp_slim/fastmcp/utilities/tests.py:505

                "No authorization response stored. redirect_handler must be called first."
            )

        response = self._stored_response

        # Extract auth code from redirect location
        if response.status_code == 302:
            redirect_url = response.headers["location"]
            parsed = urlparse(redirect_url)
            # keep_blank_values=True so explicitly-empty params (e.g. ?state=)
            # survive parsing instead of being silently dropped. Real OAuth
            # callbacks can include empty `state` or `error_description`,
            # and downstream code distinguishes "" from missing.
            query_params = parse_qs(parsed.query, keep_blank_values=True)

            if "error" in query_params:
                error = query_params["error"][0]
                error_desc = query_params.get("error_description", ["Unknown error"])[0]
                raise RuntimeError(
                    f"OAuth authorization failed: {error} - {error_desc}"
                )

            auth_code = query_params["code"][0]
            state = query_params.get("state", [None])[0]
            iss = query_params.get("iss", [None])[0]
            return AuthorizationCodeResult(code=auth_code, state=state, iss=iss)
        else:
            raise RuntimeError(f"Authorization failed: {response.status_code}")

View on GitHub (pinned to 1f02114297)

Solutions

  1. Read the error/error_description in the exception message and fix the corresponding OAuth client configuration
  2. Check client_id, redirect_uri, and scope match the provider's registered values
  3. For access_denied in tests, automate consent or use a test user that pre-approves the app
  4. Verify provider credentials (secret rotation) and tenant/issuer settings

Example fix

// before
redirect_uri = "http://localhost:9999/callback"  # not registered with provider

// after
redirect_uri = "http://localhost:6274/callback"  # registered redirect URI
Defensive patterns

Strategy: try-catch

Validate before calling

params = dict(parse_qs(urlparse(auth_url).query))
assert "client_id" in params and "redirect_uri" in params, "malformed authorization request"

Try / catch

try:
    result = await helper.callback_handler()
except RuntimeError as e:
    if "OAuth authorization failed" in str(e):
        print(e)  # e.g. access_denied - The user has denied your application access
    raise

Prevention

When it happens

Trigger: The authorization server returns error=...&error_description=... to the redirect URI — user denied consent, client_id/redirect_uri rejected, scope invalid, or account selection failed. callback_handler parses the query params and sees 'error' present.

Common situations: Misconfigured client_id or redirect URI on the provider dashboard; user cancelling the consent screen in tests; requesting scopes the app is not allowed to; expired/rotated client secrets in test fixtures.

Related errors


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