feat(oauth): Add CORS support for OAuth device flow with origin validation#107731
feat(oauth): Add CORS support for OAuth device flow with origin validation#107731
Conversation
Add CORS headers to OAuth device authorization and token endpoints to enable browser-based public clients (SPAs, browser CLIs) to complete the device authorization flow. Changes: - oauth_device_authorization.py: Add CORS headers and OPTIONS handling - oauth_token.py: Add CORS headers and OPTIONS handling - apitoken.py: Return ["*"] from get_allowed_origins() for public clients - test_apitoken.py: Add tests for get_allowed_origins() behavior Security: Access-Control-Allow-Credentials is intentionally NOT set. Public clients use bearer tokens (not cookies), so CSRF protection via origin checking is unnecessary - the token itself is the credential. Refs: - RFC 8628 §5.6: Native/public clients in device flow - RFC 6749 §2.1: Public vs confidential client types - RFC 6749 §10.3: Bearer token security model
|
is this correct? we had cors support before via allowed domains. allowing any domain to use a public credential creates a phishing attack |
We don't have a website field for public apps. We can add one and limit it to that. Not sure I see the phishing attack angle tho as we limit the cookies? |
|
Ah I understand - why would a public app (a CLI) be using the browser to query things? security vuln exists nomatter what here |
|
attack being: i take your public client ID, create a domain like sentry.gg and trick you into logging into it, masquerading as the sentry cli |
Can I not do that by just adding |
RFC 8252 §8.6 recommends public clients be bound to a registered website to prevent client impersonation attacks. An attacker could take a legitimate public client_id and use it on a phishing domain like sentry.gg to trick users into authenticating. This change restricts public OAuth clients to only allow CORS requests from their registered homepage_url. If no homepage_url is set, CORS is disabled entirely (assuming native app using device flow only). - Public client with homepage_url: CORS allowed only from that URL - Public client without homepage_url: No CORS (device flow only) - Confidential clients: Use configured allowed_origins (unchanged)
Security Fix: Bind Public Clients to
|
| Client Type | homepage_url |
CORS Allowed From |
|---|---|---|
| Public | https://cli.sentry.io |
Only that origin |
| Public | None |
No CORS (device flow from native apps) |
| Confidential | N/A | Configured allowed_origins |
For Browser CLI
The browser-based CLI will need to:
- Be hosted at a known domain (e.g.,
https://cli.sentry.io) - Have that domain registered as
homepage_urlon its public OAuth app
This binds the public client to a specific origin, preventing impersonation.
The homepage_url binding for public clients needs more discussion. For now, just add CORS headers to /oauth/device/code and /oauth/token endpoints to enable browser-based OAuth device flow.
- Add origin validation in device authorization and token endpoints - If allowed_origins is configured, only those origins are permitted - If allowed_origins is empty, all origins allowed (backwards compatible) - OPTIONS preflight still allows all origins (app unknown at that point) - Log warnings when origins are rejected for debugging
Remove backwards-compatible fallback that allowed all origins when allowed_origins was empty. Now: - allowed_origins MUST be configured for browser CORS to work - Native clients (no Origin header) don't get CORS headers - OPTIONS preflight still allows all (POST validates)
- Create OAuthCORSMixin with shared CORS logic - Reduce duplication between device authorization and token endpoints - Configurable cors_allowed_headers and cors_log_tag per endpoint
- Only allow permissive CORS headers for OPTIONS preflight requests - POST error responses (before app validation) no longer get CORS headers - Native clients without Origin header don't trigger spurious warnings This fixes two security/correctness issues: 1. POST errors with invalid client_id no longer leak error details cross-origin 2. Native CLI clients no longer generate false CORS rejection warnings
The CORS origin validation was doing a direct string comparison which failed when allowed_origins contained '*'. Now correctly allows all origins when '*' is in the allowed_origins list, consistent with is_valid_origin() behavior.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
Move Access-Control-Allow-Methods and Access-Control-Allow-Headers to only be set when Access-Control-Allow-Origin is also set. This ensures: - Native clients (no Origin header) receive no CORS headers - Rejected origins receive no CORS headers - Only valid requests (OPTIONS preflight and validated POST) get CORS headers Extracted _set_cors_headers() helper to avoid duplication.
Summary
Adds CORS (Cross-Origin Resource Sharing) support for public OAuth clients running in browsers (SPAs, browser-based CLIs) using the device authorization flow. Origins are validated against the OAuth application's
allowed_originsconfiguration.Changes
OAuthCORSMixinfor shared CORS handling logic/oauth/device/codeendpoint for device authorization flow/oauth/tokenendpoint for token exchangeallowed_originssettingMotivation
Browser-based applications using the OAuth device flow need CORS support to:
/oauth/device/code/oauth/tokenImplementation
OAuthCORSMixin (
oauth_cors_mixin.py)New mixin class that provides:
Access-Control-Max-Ageapplication.get_allowed_origins()cors_allowed_headersandcors_log_tagper endpointOAuth Endpoints
Both
oauth_device_authorization.pyandoauth_token.pynow useOAuthCORSMixin:cors_allowed_headers = "Content-Type"cors_allowed_headers = "Content-Type, Authorization"Origin Validation Behavior
allowed_originsconfigured, origin matchesallowed_originsconfigured, origin doesn't matchallowed_originsSecurity Considerations
✅ Security model:
allowed_originsfor CORS to workAccess-Control-Allow-Credentialsis intentionally NOT set❌ We intentionally do NOT:
Access-Control-Allow-Credentials: trueRFC References
Usage
To enable browser CORS for an OAuth application:
Allowed Originsfield (space-separated)