fix(oidc): return 400 instead of 500 on bad token requests

The token endpoint wrapped parse_request in try/except but
  called process_request and do_response unguarded, so a parseable-but-invalid request (e.g. a refresh_token grant missing client_id, or an
  unknown token) made idpyoidc raise and surfaced as a 500. Wrap both so failures return a clean 400 invalid_request and log the traceback
  server-side. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Johan Lundberg 2026-06-10 14:37:01 +02:00
parent 3c5451b9c2
commit b284cf596b
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
2 changed files with 42 additions and 3 deletions

View file

@ -4,6 +4,7 @@ from __future__ import annotations
import html
import json
import logging
from types import SimpleNamespace
from urllib.parse import urlencode
@ -13,6 +14,8 @@ from idpyoidc.time_util import utc_time_sans_frac
from porchlight.oidc.claims import PorchlightUserInfo, user_to_claims
logger = logging.getLogger(__name__)
router = APIRouter(tags=["oidc"])
@ -200,7 +203,7 @@ async def _complete_authorization( # noqa: PLR0913
@router.post("/token")
async def token_endpoint(request: Request) -> JSONResponse:
async def token_endpoint(request: Request) -> JSONResponse: # noqa: PLR0911
"""OIDC Token endpoint."""
oidc_server = request.app.state.oidc_server
endpoint = oidc_server.get_endpoint("token")
@ -223,14 +226,31 @@ async def token_endpoint(request: Request) -> JSONResponse:
elif hasattr(parsed, "to_dict") and "error" in parsed:
return JSONResponse(parsed.to_dict(), status_code=400)
result = endpoint.process_request(parsed)
# process_request / do_response can raise on malformed-but-parseable
# requests (e.g. a refresh_token grant missing client_id). Treat those as a
# bad request rather than letting them surface as a 500.
try:
result = endpoint.process_request(parsed)
except Exception:
logger.exception("Token endpoint failed to process request")
return JSONResponse(
{"error": "invalid_request", "error_description": "The request could not be processed"},
status_code=400,
)
if hasattr(result, "to_dict") and "error" in result:
return JSONResponse(result.to_dict(), status_code=400)
elif isinstance(result, dict) and "error" in result:
return JSONResponse(result, status_code=400)
resp_info = endpoint.do_response(response_args=result.get("response_args"), request=parsed)
try:
resp_info = endpoint.do_response(response_args=result.get("response_args"), request=parsed)
except Exception:
logger.exception("Token endpoint failed to build response")
return JSONResponse(
{"error": "invalid_request", "error_description": "The request could not be processed"},
status_code=400,
)
response_data = resp_info["response"]
if isinstance(response_data, str):