Skip to content

Per-app scopes

Every application has an allowlist of scopes it's permitted to request. When the application asks /oauth2/authorize for openid profile email users:read, the platform checks each scope against the allowlist. Scopes outside the list are dropped (or refused, depending on policy).

The allowlist is your line of defence against an application requesting capabilities it shouldn't have — whether intentionally (developer mistake) or compromised (attacker controls the app's source).

Application detail → Access tab → CapabilitiesAllowed scopes.

The form shows two columns: scopes available in this tenant on the left; scopes currently allowed for this application on the right. Drag, select-and-arrow, or type the scope name; save.

For each application, ask: what does this app's real work involve?

A typical browser SPA:

openid (always include — required for OIDC)
profile (the user's name, picture)
email (the user's email + email_verified)
offline_access (silent refresh — almost always desired for browser apps)

A typical backend API consumer (M2M):

users:read
applications:read
audit:read

A specific admin tool:

users:read
users:write
groups:read
groups:write

Don't grant users:write to an application that only reads. Don't grant audit:read to an application that has nothing to do with audit. The least-privilege instinct is what makes the allowlist worth maintaining.

When the OAuth flow includes a scope that's not on the allowlist:

  • Strict mode (default) — the platform refuses with invalid_scope. The user sees a sign-in error.
  • Permissive mode — the platform silently drops the disallowed scopes and issues a token with the rest. The application gets through but with less than it asked for.

Strict mode catches developer mistakes early. Permissive mode is forgiving but lets attacks fail silently. Default to strict.

The available scopes in your tenant are defined in Roles & scopes → Scope reference. Standard OIDC scopes (openid, profile, email, offline_access) ship by default; IntelliAuth-API scopes are pre-registered (users:read, users:write, etc.). You can add custom scopes for your own APIs.

A custom scope you add to the tenant doesn't automatically appear on any application's allowlist — you have to grant it explicitly. This is by design.

Conventions across the standard set:

  • openid — required for any OIDC flow. Always include.
  • profile — name, given_name, family_name, picture. Most apps want this.
  • email — email + email_verified. Most apps want this.
  • offline_access — request a refresh token alongside the access token. Browser apps + native apps need this; pure M2M doesn't (M2M just re-requests).
  • me:read — the signed-in user reads their own profile.
  • me:write — the signed-in user updates their own profile, manages MFA, etc.
  • users:read — admin-level read of any user record.
  • users:write — admin-level write.
  • applications:read — admin-level read of application configs.
  • applications:write — admin-level write (including secret rotation).
  • audit:read — admin-level read of the audit log.
  • groups:read / groups:write — group management.
  • resources:read / resources:write — ReBAC surface management.

Don't ship *:write if *:read would do.

If your tenant has its own APIs (your backend behind https://api.cymmetri.com), you'll define custom scopes there:

payments:read
payments:approve
inventory:read
inventory:write
analytics:export

Register them in Roles & scopes → Scope reference → New scope. Once defined, grant them to the applications that legitimately need them.

Convention: <resource>:<verb>. Conventions are stronger if your team picks one and applies it everywhere — the platform doesn't enforce a specific shape.

Audit log records every token issuance with the granted scopes. To find "which applications are requesting users:write", filter audit on oauth.token_issued and grep on the granted scopes.

This is how you find applications that no longer need a scope they're granted — clean candidates for tightening the allowlist.