Scopes & Claims

What an application may ask for, and what ends up in the token

Back to Documentation

The mental model

Four concepts, in the order they apply to a request:

ConceptQuestion it answersWhere it lives
ScopeWhat may this application ask for?Application → Allowed Scopes
ConsentWhat has the user (or org) agreed to release?Consent screen / Manage Consents
ClaimsWhat data does each granted scope actually release?Scope → Claims field
Claim sourceWhere does each claim's value come from?Built-ins, metadata, expressions, providers

A scope is a label on a bundle of claims. Requesting profile does not magically add a name to the token — it does so because the profile scope's Claims field lists name given_name family_name preferred_username. Change that field and you change what the scope releases.

Scope names and namespaces

Standard OIDC scopes are bare and reserved: openid, profile, email, phone, offline_access, roles. Scopes your organisation defines are namespaced by your organisation handle, so two organisations can both define orders:read without colliding:

# anatomy
@acme/billing:read
└─ namespace  └─ resource : action

Each scope has three forms — you normally only type the first:

FormExampleUsed for
Display@acme/billing:readWhat you type and see in the UI, tokens, consent
Global idlogin.example.com/acme/billing:readUniqueness across servers (server host is the authority)
Verifiedacme.com/billing:readOnce the org verifies a domain — portable across servers
Both forms always work. A scope created before namespacing can be requested bare (billing:read) or qualified (@acme/billing:read) — Orion treats them as the same scope when matching. Scope strings you send are never rewritten, so anything that compares token scope values literally keeps working.

The Claims field

A scope's Claims field is a space-separated list of claim names. Each name is routed to a claim source by its prefix — this is the part that is easy to miss:

PrefixExampleValue comes from
noneemailBuilt-in user/org field, else a static per-user Custom Claim
meta:meta:departmentUser metadata (app-scoped value wins over org-wide)
expr:expr:is_premiumA computed Claim Definition (sandboxed expression)
ext:ext:entitlementsAn external Claim Provider, called live or synced

The emitted claim name drops the prefix — meta:department appears in the token as department.

Built-in claim names

sub, name, given_name, family_name, preferred_username, email, phone_number, org_id, org_name, roles.

Reserved names. The built-ins above plus protocol claims (iss, aud, exp, iat, jti, scope, client_id, …) can never be produced by metadata, expressions, or external providers — a provider cannot overwrite sub and impersonate a user. sub and org_id are emitted once by the server itself.

Scope fields explained

FieldMeaning
NameThe scope identifier, e.g. orders:read. Namespaced automatically.
Display NameHuman label on the consent screen.
DescriptionThe sentence the user reads when consenting.
ClaimsSpace-separated claim names this scope releases (see above).
RequiredAlways included in tokens for the app, whether or not requested.
StandardMarks the built-in OIDC scopes. Standard scopes cannot be deleted.

Scopes with no Claims are resource scopes — they release no data, they authorise an action. devices:provision is one: holding it lets an application call the device provisioning API. Consent still applies; the API additionally checks a provisioning grant.

Worked example

An organisation wants applications to see a user's department and whether they are a manager.

  1. Set the metadata on users: Roles & Claims → member → Metadata → department = Engineering.
  2. Define a computed claim: Claims → New → name is_manager, type Boolean, expression contains(roles, "Manager").
  3. Create the scope: Scopes → New → name staff, Claims meta:department expr:is_manager.
  4. Allow it on the application: Applications → app → OAuth settings → Allowed Scopes.

A token issued for that scope then contains:

{
  "sub": "62B3…",
  "scope": "openid @acme/staff",
  "department": "Engineering",
  "is_manager": true
}

Claim values resolve at issuance, so changing a user's metadata or roles affects the next token without touching the scope. With Orion Verified tokens it affects the very next request.

If a claim is missing

  • Is the scope in the application's Allowed Scopes? If not the request fails with invalid_scope.
  • Was the scope actually requested and consented to? Check the token's scope claim.
  • Is the claim name listed in the scope's Claims field, with the right prefix?
  • Does the value exist — metadata set for that user, expression returning non-null, provider reachable?
  • Is the name reserved? Dynamic sources are silently filtered from reserved names.
  • Check https://login.shanecraven.com/oauth/{org}/userinfo — it resolves through the same pipeline as tokens.
OAuth & OIDC All Documentation