Skip to content

fix(auth): check OAuth state before trading the code - #499

Merged
simonvanlierde merged 4 commits into
mainfrom
oauth-callback-state-first
Nov 1, 2026
Merged

simonvanlierde merged 4 commits into
mainfrom
oauth-callback-state-first

Conversation

@simonvanlierde

Copy link
Copy Markdown
Contributor

Summary

The OAuth callbacks answered 500 to a made-up code, because httpx-oauth trades the code with the provider before the app checks state. This checks state first and answers 400 when the provider refuses the code.

Type of change

  • 🚀 feat: New feature
  • 🐛 fix: Bug fix
  • 🛠️ build: Build system or dependency changes
  • 🔄 ci: CI configuration changes
  • 📚 docs: Documentation only changes
  • 🏎️ perf: Performance improvement
  • ♻️ refactor: Code refactoring (no functional changes)
  • 🎨 style: Code style/formatting changes
  • ✅ test: Adding or updating tests
  • 🔧 chore: Other maintenance work

Why

  • base.py now verifies the state JWT and CSRF cookie before the code goes to GitHub or Google. A 4xx from the provider gives 400, and no answer or a 5xx gives 502. GitHub's 200 with an error body also gives 400.
  • Schemathesis now fuzzes the callbacks too: I dropped the exclude(path_regex=...) and its TODO. A fuzzed state never verifies, so the fuzz never reaches the providers.
  • The test Postgres container gets a 300 s Docker timeout. docker-py's 60 s wasn't enough when the daemon is busy.

Checklist

  • I've read the contributing guidelines
  • Code follows style guidelines and passes quality checks (just ci)
  • Unit tests added/updated and passing locally (just test)
  • Documentation updated (if applicable)
  • Database migrations created (if applicable)

Security, if relevant

  • Assets affected: OAuth login and account-linking callbacks.
  • Trust boundary changed: none. An unverified state no longer reaches the provider.
  • Secure default/failure mode: a bad state or a refused code gives 400, a provider outage gives 502, never 500.
  • Controls and tests: tests/unit/auth/test_oauth_callback.py, plus the Schemathesis fuzz on the callbacks.

Notes for reviewers

Tests: the 67 OAuth unit tests pass, and pytest tests/integration -k schemathesis passes (2 tests, 140 subtests). With the old base.py, 7 unit tests and 11 fuzz cases fail with the 500. I didn't run the full just ci. The fuzz test doesn't block the network itself, so nothing stops a future change that lets a fuzzed state reach the providers.

- Verify the state JWT and CSRF cookie before the code reaches the provider.
- Answer 400 when the provider refuses the code, including GitHub's 200 with an error body, and 502 when the provider cannot be reached.
- Fuzz the OAuth callbacks with Schemathesis: a fuzzed state never verifies, so no request reaches GitHub or Google.

Signed-off-by: Simon van Lierde <simonvanlierde@gmail.com>
Signed-off-by: Simon van Lierde <simonvanlierde@gmail.com>
docker-py gives up on an API call after 60 s. When the Docker daemon is busy, creating a container with a published port took about 90 s, so the integration tests failed before any test ran.

Signed-off-by: Simon van Lierde <simonvanlierde@gmail.com>
@codspeed

codspeed Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing oauth-callback-state-first (e88d106) with main (fc0925a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (423388d) during the generation of this report, so fc0925a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@codecov

codecov Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@simonvanlierde
simonvanlierde merged commit de8c09a into main Nov 1, 2026
26 checks passed
@simonvanlierde
simonvanlierde deleted the oauth-callback-state-first branch November 1, 2026 07:03
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.

1 participant