OAuth 2.1 — Plan and Tracking
- Dominant language
- Python
- Stars
- 44.8k
- Forks
- 4.9k
- Avg merge
- 22h 21m
- Merged PRs (30d)
- 586
Description
Note on cache semantics (clarification):
- Ensure endpoints use Django's `never_cache` for non-cacheable responses. Most of our OAuth views already do (e.g., `/oauth/authorize` via `AuthLoginView` and `/oauth/token` directly). Confirm `/oauth/userinfo` as well.
- Do not add manual `Cache-Control` or `Pragma` headers in these flows; `Pragma` is deprecated.
# OAuth 2.1 Migration — Plan and Tracking
## Summary
Modernize Sentry's OAuth implementation to comply with OAuth 2.1 specifications and security best practices. This includes PKCE enforcement, strict redirect URI validation, refresh token rotation, proper error handling, and discovery endpoints.
## Goals
- Align with OAuth 2.1 draft specification
- Improve security through PKCE, strict redirect validation, and token rotation
- Add OAuth 2.0 Authorization Server Metadata discovery (RFC 8414)
- ~~Support Dynamic Client Registration (RFC 7591/7592)~~
- Support CIMD ([RFC](datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document))
- This is now the recommended way for MCPs: [auth0.com/blog/cimd-vs-dcr-mcp-registration](auth0.com/blog/cimd-vs-dcr-mcp-registration)
- Maintain backward compatibility during transition
## Technical Requirements
### OAuth 2.1 Core Requirements
- Remove implicit grant flow (or feature flag to disable)
- Require PKCE for authorization code flow
- Enforce exact redirect URI matching (no prefix matching)
- Implement refresh token rotation with reuse detection
- Support `client_secret_basic` authentication method
- Add proper error responses per RFC 6749
### Discovery & Metadata (RFC 8414)
- `/.well-known/oauth-authorization-server` endpoint
- Expose supported grant types, response types, and PKCE methods
- Optional OIDC discovery if ID tokens are supported
### Security Requirements
- HTTPS enforcement (except loopback for native apps per RFC 8252)
- One-time authorization code usage
- Token family revocation on refresh token reuse
- No credentials in query parameters
- Proper `WWW-Authenticate` challenges for Bearer tokens
## Implementation Checklist
### Phase 1: Core Security Updates
#### Authorization Endpoint (`/oauth/authorize`)
- [x] Add PKCE support (`code_challenge`, `code_challenge_method`) (#104418)
- [x] Store PKCE challenge in `ApiGrant` model (#104418)
- [x] Implement S256 and plain challenge methods (#104418)
- [x] Enforce exact redirect URI matching (#99003)
- [x] Require `redirect_uri` when multiple URIs registered (#99004)
- [x] Support loopback redirect with ephemeral ports (RFC 8252 §8.4) (#99003)
- [ ] Validate redirect URIs (absolute, no fragments)
- [ ] Persist and echo `state` parameter in redirects
- [ ] Map errors to standard OAuth error codes
#### Token Endpoint (`/oauth/token`)
- [x] Add `client_secret_basic` authentication support (#99148)
- [x] Validate PKCE `code_verifier` against stored challenge (#104418)
- [x] Support public clients (no `client_secret`) for device flow and PKCE flows (RFC 8628 §5.6)
- [ ] Implement refresh token rotation for public clients (RFC 9700 §4.14.2)
- [ ] Detect and revoke token family on refresh token reuse
- [x] Add required response headers (`Cache-Control: no-store`, `Pragma: no-cache`) (#99148)
- [x] Return 401 with `WWW-Authenticate` header for `invalid_client` (#99148)
- [x] Implement standard error response format (#99148)
#### Public Client Support & Refresh Token Rotation
Per RFC 9700 (OAuth 2.0 Security Best Current Practice) §4.14.2:
- [x] Add `is_public` support to `ApiApplication` (nullable `client_secret`)
- [ ] Add rotation fields to `ApiToken`: `token_family_id`, `previous_refresh_token_hash`, `is_refresh_token_active`
- [ ] Allow public clients to use `grant_type=refresh_token` without `client_secret`
- [ ] Implement refresh token rotation: issue new refresh token on each use, invalidate old
- [ ] Implement replay detection: if old (rotated) refresh token is reused, revoke entire token family
- [ ] Assign token family on first refresh for legacy tokens (lazy migration)
- [ ] Add metrics for rotation events and family revocations
- [ ] Add UI option in application creation to mark app as "Public Client" (CLI/native app)
- [ ] Hide client_secret field for public clients in application detail view
#### Discovery Endpoints
- [ ] Implement `/.well-known/oauth-authorization-server`
- [ ] Return authorization server metadata per RFC 8414
- [ ] Include supported PKCE methods and client auth methods
#### Bearer Token Usage (RFC 6750)
- [ ] Audit resource endpoints for proper Bearer token handling
- [ ] Reject tokens in query parameters
- [ ] Return `WWW-Authenticate: Bearer` on auth errors
### Phase 2: Migration & Deprecation
#### Implicit Flow Removal
- [ ] Add `oauth2.1:disable-implicit` feature flag
- [ ] Return `unsupported_response_type` when disabled
- [ ] Document migration path for affected clients
#### Redirect URI Migration
- [ ] Add metrics for prefix-matched redirects
- [ ] Implement `oauth2.1:strict-redirects` feature flag
- [ ] Migration tooling for converting prefix patterns to exact URIs
- [ ] Gradual rollout plan with per-app overrides
### Phase 3: Advanced Features
#### ~~Dynamic Client Registration~~
- [ ] ~~Implement registration endpoint (RFC 7591)~~
- [ ] ~~Client management API (RFC 7592)~~
- [ ] ~~Access control via Initial Access Tokens~~
- [ ] ~~Audit logging for client operations~~
#### Client ID Metadata Document (CIMD)
- [ ] TBD
#### Token Management
- [ ] Token introspection endpoint (RFC 7662)
- [ ] Token revocation endpoint (RFC 7009)
- [ ] Refresh token scope reduction
#### Sender-Constrained Tokens via DPoP (RFC 9449)
DPoP (Demonstrating Proof of Possession) provides cryptographic sender-constraining for tokens, preventing token theft/replay even if tokens are exfiltrated.
**Server-side:**
- [ ] Parse and validate DPoP proof JWT from `DPoP` header
- [ ] Verify proof signature using embedded public key (JWK)
- [ ] Validate proof claims: `jti` (unique), `htm` (HTTP method), `htu` (URL), `iat` (timestamp)
- [ ] Implement `jti` replay cache (Redis, ~5 min window) to prevent proof reuse
- [ ] Add `dpop_jkt` field to `ApiToken` to store bound key thumbprint
- [ ] On token requests with DPoP: bind issued tokens to the proof's public key
- [ ] On refresh with DPoP: verify new proof is signed by same key as bound token
- [ ] Return `DPoP-Nonce` header for nonce-based replay protection (optional)
- [ ] Make DPoP optional: clients without DPoP header use rotation-only protection
**Resource server (API endpoints):**
- [ ] Accept `DPoP` auth scheme in `Authorization` header for DPoP-bound tokens
- [ ] Validate DPoP proof on each request for DPoP-bound access tokens
- [ ] Verify proof key thumbprint matches token's `dpop_jkt` binding
**Client requirements (for CLI/SDK implementers):**
- Generate asymmetric key pair (ES256 recommended) on first run
- Store private key securely (OS keychain, encrypted file)
- Create signed DPoP proof JWT for each token request
- Include `DPoP: ` header in token and API requests
## Data Model Changes
### ApiGrant
- `code_challenge`: Store PKCE challenge
- `code_challenge_method`: S256 or plain
### ApiApplication
- `client_secret`: Make nullable to support public clients (RFC 6749 §2.1)
- `allow_redirect_prefix_match`: Migration flag for redirect behavior
- `token_endpoint_auth_method`: Client authentication method
- `grant_types`: Supported grant types
- `response_types`: Supported response types
- `require_pkce`: PKCE enforcement control
### ApiToken
- `token_family_id`: UUID linking tokens in a rotation chain (for replay detection)
- `previous_refresh_token_hash`: Hash of rotated-out refresh token (for replay detection)
- `is_refresh_token_active`: Whether this token's refresh_token can still be used
- `dpop_jkt`: JWK thumbprint of bound DPoP key (for sender-constrained tokens, future)
## Rollout Strategy
### Feature Flags
- `oauth2.1:strict-redirects`: Enable exact redirect matching
- `oauth2.1:disable-implicit`: Disable implicit flow
- `oauth2.1:require-pkce`: Enforce PKCE
- `oauth2.1:dynamic-registration`: Enable client registration
### Migration Path
1. Deploy with all flags OFF (backward compatible)
2. Enable logging/metrics for deprecated behaviors
3. Gradual flag enablement with per-app overrides
4. Remove legacy code after full migration
## Related Work
- #104418 - feat(oauth): Add PKCE support for OAuth2 authorization code flow (MERGED)
- #99148 - feat(oauth): client_secret_basic, RFC errors, cache headers (MERGED)
- #99003 - Add strict redirect URI validation mode (MERGED)
- #99004 - Require redirect_uri when multiple URIs registered (MERGED)
## References
- [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) - OAuth 2.0 Authorization Framework
- [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) - PKCE
- [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628) - Device Authorization Grant
- [RFC 9700](https://datatracker.ietf.org/doc/html/rfc9700) - OAuth 2.0 Security Best Current Practice
- [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449) - DPoP (Demonstrating Proof of Possession)
- [OAuth 2.1 Draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1)
## Success Criteria
- Full OAuth 2.1 compliance
- Zero breaking changes for existing clients during migration
- 100% test coverage for OAuth flows
- Security audit passed
Contributor guide
Assessment
This issue has not been assessed yet.