Scopes
Scopes are the space-separated permission strings that 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 that 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 determine whether a refresh_token is issued. That depends on the client's refresh_token grant (see Refresh tokens) |
These scopes control whether the matching claims appear 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 that 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 scope catalogue lists the resource names
(users, roles, groups, clients, webhooks, audit, social, agents, permissions,
settings, analytics, sessions, abac) and what each one unlocks. 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 that 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 can never be granted to
organization API keys (lmk_): they act as a person, not as an integration.
Some routes need no user scope: the OAuth/OIDC protocol endpoints under /oauth/, dynamic
client registration, /.well-known/* discovery, SCIM (its own token), AAuth, the challenge
endpoint for MCP, workload attestation and the public agent card. The same applies to 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 capability model for agents 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 page for the scope catalogue; there you list the scopes that an application may request under Applications → your app → Configuration → Allowed scopes.) The endpoints are:
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 one returns 409 conflict with
"message": "A scope with this name already exists" when the name already exists in your
organization or matches a system scope (openid, profile, admin, and so on). 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.