Skip to content

Commit 67af227

Browse files
Improve robustness and error handling in KeycloakAuthProvider
Address CodeRabbit review feedback: - Add 10s timeout to OIDC discovery to prevent startup hangs - Add 10s timeout to httpx client for registration requests - Improve error handling: wrap response.json() in try/catch and preserve Keycloak error details in error_description field - Simplify URL conversions: remove redundant None checks after discovery applies defaults (keep str() conversions for type safety)
1 parent ccc8b71 commit 67af227

File tree

1 file changed

+24
-12
lines changed

1 file changed

+24
-12
lines changed

src/fastmcp/server/auth/providers/keycloak.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,10 @@ def __init__(
140140

141141
# Create default JWT verifier if none provided
142142
if token_verifier is None:
143+
# After discovery, jwks_uri and issuer are guaranteed non-None (defaults applied)
143144
token_verifier = JWTVerifier(
144-
jwks_uri=str(self.oidc_config.jwks_uri)
145-
if self.oidc_config.jwks_uri
146-
else None,
147-
issuer=str(self.oidc_config.issuer)
148-
if self.oidc_config.issuer
149-
else None,
145+
jwks_uri=str(self.oidc_config.jwks_uri),
146+
issuer=str(self.oidc_config.issuer),
150147
algorithm="RS256",
151148
required_scopes=settings.required_scopes,
152149
audience=None, # Allow any audience for dynamic client registration
@@ -164,7 +161,7 @@ def _discover_oidc_configuration(self) -> OIDCConfiguration:
164161
# Fetch original OIDC configuration from Keycloak
165162
config_url = AnyHttpUrl(f"{self.realm_url}/.well-known/openid-configuration")
166163
config = OIDCConfiguration.get_oidc_configuration(
167-
config_url, strict=False, timeout_seconds=None
164+
config_url, strict=False, timeout_seconds=10
168165
)
169166

170167
# Apply default values for fields that might be missing
@@ -280,7 +277,7 @@ async def register_client_proxy(request):
280277
body = json.dumps(registration_data).encode("utf-8")
281278

282279
# Forward the registration request to Keycloak
283-
async with httpx.AsyncClient() as client:
280+
async with httpx.AsyncClient(timeout=10.0) as client:
284281
logger.info(
285282
f"Forwarding client registration to Keycloak: {self.oidc_config.registration_endpoint}"
286283
)
@@ -300,12 +297,27 @@ async def register_client_proxy(request):
300297
)
301298

302299
if response.status_code != 201:
303-
return JSONResponse(
304-
response.json()
300+
error_detail = {"error": "registration_failed"}
301+
try:
305302
if response.headers.get("content-type", "").startswith(
306303
"application/json"
307-
)
308-
else {"error": "registration_failed"},
304+
):
305+
error_detail = response.json()
306+
else:
307+
error_detail = {
308+
"error": "registration_failed",
309+
"error_description": response.text[:500]
310+
if response.text
311+
else f"HTTP {response.status_code}",
312+
}
313+
except Exception:
314+
error_detail = {
315+
"error": "registration_failed",
316+
"error_description": f"HTTP {response.status_code}",
317+
}
318+
319+
return JSONResponse(
320+
error_detail,
309321
status_code=response.status_code,
310322
)
311323

0 commit comments

Comments
 (0)