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
- Read the error/error_description in the exception message and fix the corresponding OAuth client configuration
- Check client_id, redirect_uri, and scope match the provider's registered values
- For access_denied in tests, automate consent or use a test user that pre-approves the app
- 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
- Keep client_id/redirect_uri/scope in sync with the provider dashboard
- Pre-approve test users to avoid access_denied
- Check error_description in the message before debugging code
- Rotate secrets on schedule to avoid invalid_client
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
- No authorization response stored. redirect_handler must be c
- Authorization failed: {response.status_code}
- OAuth client not found - cached credentials may be stale
- OAuth server rejected the static client credentials. Verify
- Assertion must include exp claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/0c57c65f3b9afb87.
Report an issue: GitHub.