Skip to content

Conversation

@dcramer
Copy link
Member

@dcramer dcramer commented Dec 4, 2025

Implements RFC 7636 Proof Key for Code Exchange (PKCE) to protect OAuth2 authorization code grants from interception attacks. Adds code_challenge and code_challenge_method support to the authorize endpoint, and validates code_verifier in the token endpoint. Supports both S256 and plain methods.

Refs #99002

Replaces #99452

@dcramer dcramer requested review from a team as code owners December 4, 2025 21:07
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Dec 4, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

This PR has a migration; here is the generated SQL for src/sentry/migrations/1012_add_pkce_to_apigrant.py

for 1012_add_pkce_to_apigrant in sentry

--
-- Add field code_challenge to apigrant
--
ALTER TABLE "sentry_apigrant" ADD COLUMN "code_challenge" varchar(128) NULL;
--
-- Add field code_challenge_method to apigrant
--
ALTER TABLE "sentry_apigrant" ADD COLUMN "code_challenge_method" varchar(10) NULL;

@codecov
Copy link

codecov bot commented Dec 4, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
30797 2 30795 246
View the top 2 failed test(s) by shortest run time
tests.sentry.web.frontend.test_oauth_token.OAuthTokenCodeTest::test_inactive_application_rejects_token_creation
Stack Traces | 0.379s run time
#x1B[1m#x1B[.../web/frontend/test_oauth_token.py#x1B[0m:330: in test_inactive_application_rejects_token_creation
    assert resp.status_code == 400
#x1B[1m#x1B[31mE   assert 401 == 400#x1B[0m
#x1B[1m#x1B[31mE    +  where 401 = <HttpResponse status_code=401, "application/json">.status_code#x1B[0m
tests.sentry.web.frontend.test_oauth_token.OAuthTokenRefreshTokenTest::test_inactive_application_rejects_token_refresh
Stack Traces | 0.583s run time
#x1B[1m#x1B[.../web/frontend/test_oauth_token.py#x1B[0m:659: in test_inactive_application_rejects_token_refresh
    assert resp.status_code == 400
#x1B[1m#x1B[31mE   assert 401 == 400#x1B[0m
#x1B[1m#x1B[31mE    +  where 401 = <HttpResponse status_code=401, "application/json">.status_code#x1B[0m

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Moves PKCE validation and OAuth security checks from the endpoint layer to the
model layer. The oauth_token endpoint now calls ApiToken.from_grant() instead
of duplicating token creation logic with .objects.create().

Enhances from_grant() to handle redirect_uri binding and PKCE verification
inside the lock, fixing TOCTOU race conditions where an application could be
deactivated or grant could be reused between validation and token creation.

All validations now properly invalidate grants on failure per RFC 6749 §10.5.
@dcramer
Copy link
Member Author

dcramer commented Dec 18, 2025

i think we're good to go

Copy link
Contributor

@michelletran-sentry michelletran-sentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM. There's just the one question about removing the status filter for getting ApiApplication.

# once they login, bind their user ID
if request.user.is_authenticated:
request.session["oa2"]["uid"] = request.user.id
# Regenerate session to prevent session fixation attacks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this prevents attackers (who somehow has the user's session id before they're logged in) from reusing that session after the user has logged in (so now, the attacker can call /authorize again and it will see the user as already authenticated). I'm not sure whether it's simple to get/inject a session id onto a Sentry cookie, so this might not be that exploitable. But from what I've read (on ChatGPT and other sources), cycling the session id after authenticating is a best practice on this endpoint.

@dcramer
Copy link
Member Author

dcramer commented Dec 19, 2025

YOLO

@dcramer dcramer merged commit 2a8e629 into master Dec 19, 2025
68 checks passed
@dcramer dcramer deleted the oauth21-pkce branch December 19, 2025 20:27
@github-actions github-actions bot locked and limited conversation to collaborators Jan 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants