Skip to content

feat(api): Advertise required scopes on token-scope 403s (RFC 6750)#118612

Draft
gricha wants to merge 1 commit into
masterfrom
greg/insufficient-scope-errors
Draft

feat(api): Advertise required scopes on token-scope 403s (RFC 6750)#118612
gricha wants to merge 1 commit into
masterfrom
greg/insufficient-scope-errors

Conversation

@gricha

@gricha gricha commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

When a token-authorized request is denied for lacking the required scope, Sentry returns a
bare 403 {"detail": "You do not have permission to perform this action."} — it never says
which scope was needed. This adopts the OAuth 2.0 standard answer (RFC 6750
insufficient_scope) so callers know what they're missing:

WWW-Authenticate: Bearer error="insufficient_scope", scope="org:admin org:write"

The required scopes come from the endpoint's own scope_map, surfaced from the single
shared token-scope gate (ScopedPermission.has_permission), so every token-scoped
endpoint gets it with no per-class edits.

Why it's safe / non-breaking

  • The 403 body is unchanged — the scope info rides only in the WWW-Authenticate
    header, so clients parsing {"detail": ...} are unaffected.
  • No-op for non-token auth (session/superuser/staff) and for 401 authentication failures.
  • The transport already existed: custom_exception_handler copies an exception's
    auth_header onto WWW-Authenticate (it even cited RFC 6750).
  • Discloses only the endpoint's required scopes (already public in open-source scope_map
    and the API docs). The denial fires at the view level before the org/object is loaded,
    so it leaks no resource existence; it never enumerates the caller's held scopes.

Non-goals

  • Object-level scope denials (has_object_permission) are not enriched yet — deliberate,
    to keep the blast radius small. The view-level gate covers the motivating cases.

Tests

tests/sentry/api/test_permissions.py: unit (the raise + exact header at the permission
boundary) and end-to-end (header reaches the HTTP response; body unchanged; no header
on 401, on session denial, or on success).

Spec: openspec/changes/add-insufficient-scope-errors/.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 26, 2026
@gricha gricha force-pushed the greg/insufficient-scope-errors branch from 8ce455b to 623301f Compare June 27, 2026 01:28
When a token-authorized request is denied because its scopes do not cover the
endpoint's required scopes, return an RFC 6750 insufficient_scope challenge in the
WWW-Authenticate header instead of a bare 403. The required scopes come from the
endpoint's own scope_map, surfaced from the single shared token-scope gate
(ScopedPermission.has_permission), so every token-scoped endpoint benefits with no
per-class edits.

The response body is unchanged and the behavior is a no-op for non-token auth and for
401s, so this is non-breaking. Sentry's custom_exception_handler already forwards an
exception's auth_header onto WWW-Authenticate, so the transport is reused as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gricha gricha force-pushed the greg/insufficient-scope-errors branch from 623301f to 27a0106 Compare June 27, 2026 17:23
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.

1 participant