security
Designing OAuth2 scopes
Why `user:read` isn't enough, and how to scope tokens without painting yourself into a corner.
Scopes are the access-control language of your API. Get them right and third-party integrations feel natural. Get them wrong and you're forever explaining to customers why the Slack bot can also delete their billing history.
The read/write split is the minimum#
At minimum, split every resource into read:* and write:*. read:cases lets an integration list cases; write:cases lets it mutate them. This covers 80% of real-world needs without over-engineering.
Resource-first naming#
{verb}:{resource} scales better than flat names. read:billing is clear; billing_read_only is not. Clients see a list of scopes on the consent screen and need to understand what they're granting at a glance.
Granular scopes are a trap#
It's tempting to ship read:cases.priority, write:cases.status, write:cases.comments. Developers building against your API hate this. They'll request everything, always. Offer coarse scopes by default, introduce fine-grained ones only when a customer has a real reason.
Admin-style scopes#
Some operations — transferring org ownership, deleting accounts — should require explicit admin scopes (admin:org). Never bundle admin actions under a regular write:* scope; a compromised integration shouldn't be able to delete the account that installed it.
Scope inheritance#
admin:* should imply write:* which implies read:*. Keep the hierarchy simple. Resist the urge to invent a DAG.
Document the minimum#
For each API endpoint, document the exact scopes required. An SDK that surfaces this to the developer ("this call requires write:billing") saves hours of consent-screen iteration.