Scopes
Scopes are the space-separated permission strings a client sends in the scope parameter when requesting authorization (each one names a kind of access, such as reading the user's e-mail address). LumoAuth displays the requested scopes on the consent screen so the user knows what access they are granting. A token is issued only for the intersection of requested scopes and scopes the client is allowed to request.
Standard OIDC Scopes
| Scope | Claims returned |
|---|---|
openid | sub — required for all OIDC flows and for UserInfo with a user token |
profile | name, given_name, family_name, picture, locale, updated_at |
email | email, email_verified |
phone | phone_number, phone_number_verified |
address | address object |
offline_access | Accepted and advertised; it does not decide whether a refresh_token is issued. That depends on the client's refresh_token grant (see Refresh tokens) |
These scopes gate the matching claims in the ID token, UserInfo and JWT access tokens: an access token carries email only with email, name and other profile claims only with profile, and so on.
LumoAuth Extension Scopes
| Scope | Description |
|---|---|
roles | Accepted in requests, but does not add role names by itself: roles appears in access tokens and UserInfo only for clients that enable User roles in their JWT claims (see Claims and roles). The ID token never carries roles |
groups | Include the user's groups |
permissions | Include the user's granted permissions |
The discovery document's scopes_supported lists only the standard OIDC scopes. These extension scopes (and any custom scopes you create) are valid in scope requests but are not advertised in discovery.
Admin API scopes
An access token can be allowed to call the Admin API by
carrying an admin scope. These scopes are only granted to OAuth clients created in the portal:
a dynamically registered client never receives admin, admin:*, *:write,
mcp:* or offline_access, even if it asks for them.
| Scope | Grants |
|---|---|
admin | Every Admin API resource, limited to what the signed-in user may do: each endpoint still requires its own permission |
admin:<resource>:read | Read endpoints of one resource, for example admin:users:read |
admin:<resource>:write | Read and write endpoints of one resource, for example admin:users:write |
The resource names (users, roles, groups, clients, webhooks, audit, social,
agents, permissions, settings, analytics, sessions, abac) and what each unlocks
are listed in the scope catalogue. Scopes are
enforced per resource, and a :read scope never authorises a POST, PUT, PATCH or
DELETE. A token without any admin scope gets 403 forbidden on the Admin API, even for an
administrator.
User API scopes
The organization APIs outside /admin act on the signed-in user's own account or on
organization data the user may see. Since 2026-09-25 every such route requires an
explicit scope when it is called with an access token; a token with only
openid profile email gets 403 forbidden (This endpoint does not accept API keys or access tokens. when the route takes no credential at all). Request the scope in the
authorization request and allow it on the client, like any other scope. The consent screen
describes each one.
| Scope | Unlocks |
|---|---|
mfa:read | GET /me/authenticators, GET /me/recovery-codes, GET /me/trusted-devices |
mfa:manage | Everything under /me/authenticators, /me/recovery-codes, /me/trusted-devices (enroll, verify, rename, remove, regenerate) and /mfa/challenges |
mfa:challenge | Start, poll and answer step-up challenges at /mfa/challenges |
abac:read | POST /abac/check, POST /abac/check-bulk, GET /abac/my-attributes, GET /abac/attribute-definitions, GET /abac/resources/{type}/{id}/attributes |
abac:write | PUT /abac/users/{userId}/attributes/{slug} and PUT /abac/resources/{type}/{id}/attributes/{slug}; the user must also hold abac.manage |
mcp:read | GET /mcp/servers and GET /mcp/servers/{serverId} |
jit:approve | GET /jit/pending, POST /jit/approve/{requestId}, POST /jit/deny/{requestId}; the user must also hold jit.approve. See JIT: who can approve |
Paths are relative to /orgs/{orgId}/api/v1. These scopes are never grantable to
organization API keys (lmk_): they act as a person, not as an integration.
Routes that need no user scope: the OAuth/OIDC protocol endpoints under /oauth/, dynamic
client registration, /.well-known/* discovery, SCIM (its own token), AAuth, the MCP
challenge endpoint, workload attestation, the public agent card, GET /me,
GET /agents/me, POST /agents/ask and the self-decision Zanzibar check/expand (asking
about another subject still needs authz:check). Agent and JIT credentials are governed by
the agent capability model instead of these scopes.
Agent registration (POST /agents/register) with an access token needs admin or
admin:agents:write; see Agent Registry.
Fresh sign-in for sensitive calls
POST /me/authenticators/{type}/enroll and POST /me/recovery-codes also require the
token's auth_time to be at most 10 minutes old. Otherwise the answer is
401 insufficient_user_authentication with
WWW-Authenticate: Bearer error="insufficient_user_authentication", …, max_age=600
(a step-up authentication challenge). Re-run the authorization request with max_age=600 and retry. See the
MFA API.
Common mistakes
403on/me/authenticatorswith a token that works on/oauth/userinfo. The token has nomfa:read/mfa:manage. Add the scope to the client and request it.403on/jit/approvewith a working token. The token lacksjit:approve, the user lacks thejit.approvepermission, or the token is a JIT, agent or delegated token.401 insufficient_user_authenticationon enroll. The user signed in more than 10 minutes ago. Send them through/authorizewithmax_age=600.
Custom Scopes
Create organization-specific scopes via the admin API (the portal has no scope catalogue page; there you list the scopes an application may request under Applications → your app → Configuration → Allowed scopes):
GET|POST /orgs/{orgId}/api/v1/admin/scopes
DELETE /orgs/{orgId}/api/v1/admin/scopes/{scopeId}
GET lists scopes and POST creates one on /admin/scopes; deletion targets a specific scope by ID on /admin/scopes/{scopeId}.
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/scopes \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"name": "read:reports",
"description": "Read access to financial reports",
"isDefault": false
}'
Custom scopes must be explicitly allowed per OAuth client.
Scope names are unique within an organization. Creating a name that already exists in your
organization, or that matches a system scope (openid, profile, admin, and so on), returns
409 conflict with "message": "A scope with this name already exists". Another organization
may use the same custom name; scopes never collide across organizations.
The endpoint requires admin:permissions:write (listing requires admin:permissions:read).
Requesting Scopes
Include scopes as a space-separated list in the scope parameter:
scope=openid profile email read:reports
LumoAuth grants only the intersection of requested scopes and scopes allowed for the client. The scope field in the token response reflects what was actually granted.
Related
- OAuth Clients — configure per-client allowed scopes
- Token formats — how granted scopes appear in tokens
Standards used on this page
| Plain name | Standard |
|---|---|
| OpenID Connect (OIDC) | OpenID Connect Core 1.0 |
| Dynamic client registration | RFC 7591 and RFC 7592 |
| Step-up authentication challenge | RFC 9470 |
See the standards reference for what each one covers and how LumoAuth uses it.