Skip to content

OAuth2 token fetch crashes with JSONDecodeError instead of a catchable error on rate-limit responses - #929

Open
juneja-varun wants to merge 2 commits into
authlib:mainfrom
juneja-varun:fix/oauth2-non-json-error-response
Open

OAuth2 token fetch crashes with JSONDecodeError instead of a catchable error on rate-limit responses#929
juneja-varun wants to merge 2 commits into
authlib:mainfrom
juneja-varun:fix/oauth2-non-json-error-response

Conversation

@juneja-varun

Copy link
Copy Markdown

Fixes #928.

I'm calling a token endpoint that sits behind an API gateway. When the gateway rate-limits me, it sends back a 429 with a plain-text body (completely normal gateway behavior). Instead of getting something I could catch and retry on, fetch_token() crashed with an unhandled json.decoder.JSONDecodeError.

What's actually happening

OAuth2Client.parse_response_token() only calls resp.raise_for_status() for status codes >= 500. For anything below that, it goes straight to resp.json() - so a 4xx response with a non-JSON body (rate-limit pages, WAF blocks, HTML error pages, plain text) blows up with a raw JSONDecodeError instead of a meaningful, catchable error.

Reproduction

Using the exact repro from the issue (a local server returning 429 with a plain-text body):

json.decoder.JSONDecodeError: Extra data: line 1 column 5 (char 4)

Fix

Wrap the resp.json() call in a try/except. On a JSON decode failure, call resp.raise_for_status() so the caller gets a proper httpx.HTTPStatusError describing the actual HTTP status instead of a JSON parsing error.

I deliberately didn't go with the issue's first suggested option (calling raise_for_status() unconditionally before parsing) - that would break the normal OAuth2 error flow, where a 400 response with a well-formed {"error": "invalid_grant", ...} body is supposed to parse into a friendly OAuthError, not an immediate HTTPStatusError. Verified this explicitly: a 400 with a valid JSON error body still raises OAuthError(error='invalid_grant', ...) exactly as before, only the malformed-body case changes.

Testing

  • Added test_fetch_token_non_json_error_response in tests/clients/test_httpx/test_oauth2_client.py, confirmed it fails on unpatched code with the exact reported JSONDecodeError and passes with the fix.
  • Manually verified no regression to the existing OAuth2 error-response flow (400 + valid JSON body still raises the expected OAuthError).
  • Full suite: 953 passed, 4 skipped.
  • ruff check and ruff format --check: both clean.

@juneja-varun

Copy link
Copy Markdown
Author

@lepture the failing tests were a real gap on my end — this repo enforces 100% diff coverage and my fix's re-raise branch (only reachable on a 2xx response with a malformed body) wasn't covered. Added a test for that case and confirmed locally: full suite passes (954 passed, 4 skipped), diff coverage is 100%, and ruff is clean. Could you approve the workflow run again when you get a chance?

@juneja-varun

Copy link
Copy Markdown
Author

Just following up — let me know if there's anything else needed on my end before the workflow can be re-approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSONDecodeError raised instead of a HTTP error

1 participant