Scopes
Overview
Scopes define what your app can access on behalf of a user. They are declared when you register your app and shown to the user on the consent screen. Users grant or deny each scope individually. Request only what you need.
Available scopes
| Scope | What it grants |
|---|---|
| identity | Returns sub, an opaque stable user ID. It is unique per user and safe to store as your own key. It is not an email address and not a name. |
| avatar | Returns avatar_url, the user's profile photo. |
| theme | Returns theme, so your interface can open in the light or dark mode the user already prefers. |
| profile | Umbrella covering all three above. Granting it is equivalent to granting identity, avatar and theme together. |
Declaring scopes
Scopes are set in the developer dashboard when you create or edit your app. They cannot be changed at runtime.
Which is the right behaviour, even though it reads as a trap: a scope somebody approved is the only scope you should have, and quietly widening it later would make consent meaningless. But it does mean adding a scope after launch is a migration rather than an edit.
Two ways through it. Ship the scope and let it apply to new connections, accepting that existing users will not have it until they reconnect for some other reason. Or revoke the affected grants from your dashboard, which sends them through consent again on their next visit, where they see the new scope and approve it explicitly.
/oauth/userinfo returns an X-Cladior-Scopes-Missing header listing any scope
your app now requests that this particular grant does not cover. If a field you
expect is absent, that header says why.Reading what you were granted
There is no list of granted scopes to read. You find out what you were given by calling the userinfo endpoint and seeing which fields came back: a scope you were not granted simply produces no field.
const res = await fetch(
`https://www.cladior.com/oauth/userinfo?site=${APP_ID}`,
{ headers: { Authorization: `Bearer ${sessionToken}` } },
);
const info = await res.json();
// identity granted -> info.sub is set
// avatar granted -> info.avatar_url is set
// theme granted -> info.theme is set
if (info.avatar_url) showAvatar(info.avatar_url);
// Absent field you were expecting? This names the reason.
res.headers.get('X-Cladior-Scopes-Missing'); Treat every field as optional. If your product needs one it did not get, say what the permission is for and offer to reconnect, rather than assuming it is present.
Scope minimization
The consent screen shows every scope you declared. A long list of scopes reduces conversion. Request only what your product needs at launch. You can add more scopes later as you ship features that require them.
The true minimum is none at all. Billing runs through the proxy without any scope, so
an app that only meters usage can request nothing and still work. Add identity when you need a stable key to store your own
data against, and the other two only when you actually use them.