Skip to content

Conversation

@betegon
Copy link
Member

@betegon betegon commented Jan 13, 2026

Summary

Fixes the OAuth 2.0 Device Authorization Grant implementation to properly support public clients as required by RFC 8628.

Problem

The current token endpoint requires client_secret for ALL grant types, including device_code. This breaks the fundamental design of the device flow, which is explicitly designed for public clients (CLIs, native apps, IoT devices) that cannot securely store secrets.

What the RFC Says

RFC 8628 §5.6 - Non-Confidential Clients:

Device clients are generally incapable of maintaining the confidentiality of their credentials, as users in possession of the device can reverse-engineer it and extract the credentials. Therefore, unless additional measures are taken, they should be treated as public clients (as defined by Section 2.1 of [RFC6749]).

RFC 8628 §3.4 - Device Access Token Request:

client_id - REQUIRED if the client is not authenticating with the authorization server as described in Section 3.2.1. of [RFC6749].

RFC 6749 §2.1 - Client Types:

public - Clients incapable of maintaining the confidentiality of their credentials (e.g., clients executing on the device used by the resource owner, such as an installed native application or a web browser-based application)

Solution

  • Move grant_type validation before credential check (needed to determine auth requirements)
  • For device_code grant: only require client_id (public client mode per RFC 8628 §5.6)
  • If client_secret is provided: still validate it (supports confidential clients that choose to authenticate)
  • Other grant types (authorization_code, refresh_token): unchanged, still require client_id + client_secret

Changes

File Change
src/sentry/web/frontend/oauth_token.py Allow public client auth for device_code grant
tests/sentry/web/frontend/test_oauth_token.py Added 5 new tests for public client support

New Tests

  1. test_public_client_success - Public client can exchange approved device code
  2. test_public_client_invalid_client_id - Invalid client_id rejected with 401
  3. test_public_client_missing_client_id - Missing client_id rejected with 401
  4. test_public_client_authorization_pending - Polling works for public clients
  5. test_confidential_client_wrong_secret_rejected - Wrong secret still rejected when provided

Security Considerations

This change is RFC-compliant and does not reduce security:

  1. Device code binding: The device_code is bound to the application at creation time, so a public client can only poll for tokens for its own application
  2. User authorization: The user must still explicitly approve the request via the browser
  3. Confidential clients still supported: If a client provides client_secret, we validate it
  4. Rate limiting: Existing rate limiting on device code polling remains in place

Related

@betegon betegon requested a review from a team as a code owner January 13, 2026 12:16
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Jan 13, 2026
@betegon betegon marked this pull request as draft January 13, 2026 12:25
@betegon betegon force-pushed the fix/oauth-device-flow-public-clients branch from aea7b91 to 617ac41 Compare January 13, 2026 12:25
The OAuth 2.0 Device Authorization Grant (RFC 8628) is designed for
headless clients like CLIs, native apps, and IoT devices that cannot
securely store credentials.

Per RFC 8628 §5.6 (Non-Confidential Clients):
> Device clients are generally incapable of maintaining the
> confidentiality of their credentials, as users in possession of
> the device can reverse-engineer it and extract the credentials.
> Therefore, unless additional measures are taken, they should be
> treated as public clients.

And per RFC 8628 §3.4 (Device Access Token Request):
> client_id: REQUIRED if the client is not authenticating with the
> authorization server as described in Section 3.2.1. of [RFC6749].

The previous implementation required client_secret for ALL grant
types, including device_code. This broke the fundamental design of
the device flow, which is meant for public clients that authenticate
with only client_id.

Changes:
- Move grant_type validation before credential check
- For device_code grant: only require client_id (public client mode)
- If client_secret is provided: still validate it (confidential client)
- Other grant types: unchanged (still require client_id + client_secret)

This allows CLIs to authenticate without embedding secrets while still
supporting confidential clients that choose to provide credentials.
@betegon betegon force-pushed the fix/oauth-device-flow-public-clients branch from 1464771 to 72a582f Compare January 13, 2026 12:28
@BYK BYK marked this pull request as ready for review January 13, 2026 13:03
@BYK BYK merged commit c677394 into master Jan 13, 2026
66 checks passed
@BYK BYK deleted the fix/oauth-device-flow-public-clients branch January 13, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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