Authorization
Scopes and grants — what an access token is allowed to do, and how that is decided on every call.
What your application may do is decided by grants and scopes. A grant is minted when a user consents during the authorization flow; scopes are the capabilities it carries.
Scopes
Scopes are a closed set. They are what your app requests at
/oauth/authorize, and they map one-to-one to the
capabilities enforced on the Resource API.
| Scope | Grants |
|---|---|
openid | OIDC identity — returns an id_token. Carries no Resource API capability. |
mail.read | The read surface: event list / get, mailbox listing, and explicit history synchronizations (AccountSynchronizations/*). A history synchronization re-fetches history from the provider — a read that appends locally and changes nothing upstream, so it stays in this scope. |
mail.modify | Message flags, moves, archive/trash/spam/delete, draft writes, mailbox create/rename/delete, and modify-purpose staged uploads. |
mail.send | Send-purpose staged uploads plus durable submission send/get. |
accounts.read | Read the user's mailboxes. |
accounts.update | Change a mailbox's personal presentation (Accounts/update). |
accounts.delete | Purge a mailbox (Accounts/purge). |
bindings.update | Disconnect a mailbox (Accounts/disconnect) and attach or reconnect service bindings. |
accounts.create | Connect a new mailbox. Global-only — automatically carries global accounts.{read,update,delete}. |
accounts.create makes no sense scoped to one existing mailbox, so it requires
the "all my mailboxes" selector and auto-carries the other accounts.*
capabilities, letting your app manage the mailboxes it creates.
Required and recommended scopes
An application's registered scopes are its ceiling: an authorization
request may ask for any subset, but never for a scope outside that set. The app
owner also chooses a required_scopes subset. Every other registered scope is
recommended; the consent screen labels these Optional for the user.
On consent, all requested scopes start selected:
- Required scopes stay selected and cannot be unchecked.
- Recommended scopes, labeled Optional, start selected, but the user may remove them.
Required scopes are a protocol rule, not just a disabled checkbox. Supermail enforces this relationship when the app is created or updated, when authorization starts, when consent is approved, and again when tokens are issued:
required ⊆ granted ⊆ requested ⊆ registeredAn authorization request that omits one of the app's required scopes fails
with invalid_scope. To present every recommended capability as the default,
request the app's full registered scope set.
Grants
A grant records (user, app, account-selector, scopes). When the user
consents, they choose which mailboxes your app reaches:
- A single mailbox — the grant covers exactly that account.
- All of the user's mailboxes — the grant covers every account they own, now and in the future.
There are no mailbox groups. To reach several specific mailboxes, an app holds
several single-account grants, so one (user, app) pair may hold multiple
grants at once.
Grants are revoked, never edited:
- Widening (more scopes or more mailboxes) is re-consent on the difference — it supersedes the old grant with a new one.
- Narrowing or revoking is unilateral, needs no involvement from your app, and takes effect on the next API call.
Live enforcement
Every Resource API call runs the same check:
- Verify the bearer token (signature, expiry, issuer).
- Re-resolve the live grant named by the token's
grant_id. - Confirm the grant covers the target account with the required capability.
- For writes, confirm the account's working capability also admits the verb.
- Perform the request.
Because step 2 reads the current grant — not the token's scope claim — a
revoke or narrowing is enforced immediately, inside the access token's
remaining lifetime. A widening waits for the user to re-consent.
Delegated login grants
Delegated portal login uses a separate kind of
grant carrying the internal delegated_login capability. It proves that one
trusted confidential app may sign one app-local subject into the portal. It
does not grant Resource API access, does not appear in an OAuth access token,
and is not part of the scope set requested at /oauth/authorize.
The delegated grant is projected as a login method in the user's profile. Removing that method revokes the grant and its outstanding login tickets.
The service-binding axis (attaching, disconnecting, reconnecting) is independent of a grant's scopes. Disconnecting a mailbox does not revoke a grant, and revoking a grant does not disconnect a mailbox. An authenticated external resource has one durable reservation owner. Conflicting attachment fails without identifying that owner; disconnect keeps the reservation and account purge releases it.