Roles and RBAC
System roles, custom roles built from a fixed permission catalog, and an anti-escalation invariant that makes privilege escalation structurally impossible.
Roles govern who can do what in the admin console and the Admin API of your organization. The model is deny-by-default: a permission absent from a role is refused, server-side, on every endpoint.
System roles
Every member of an organization holds exactly one role. Three system roles are built in:
| Role | Meaning |
|---|---|
owner | Full control, including assigning or revoking the owner role |
admin | Day-to-day administration, all catalog permissions |
member | Ordinary user, no admin permission at all |
owner and admin currently hold the same permission set; the difference is that only an owner can assign or revoke the owner role. A member sees no admin console beyond their own profile.
The permission catalog
Custom roles are built from a fixed, server-defined catalog. Any permission outside it is rejected.
| Key | Grants |
|---|---|
users:view | View the user directory |
apps:manage | Create, edit and delete OAuth applications |
audit:view | Read the audit log |
members:manage | Assign and revoke member roles |
tenant:manage | Tenant settings, policies, SSO, domains, branding |
roles:manage | Define custom roles |
groups:manage | Manage user groups |
Custom roles
A custom role has a key (a lowercase slug of 2 to 40 characters matching ^[a-z][a-z0-9-]{1,39}$; owner, admin and member are reserved), a display name and any subset of the catalog.
Resolution is data-driven: when a request is authorized, the member's role string resolves either to the static system table or to the custom role stored for the tenant. An unknown or deleted role resolves to the empty permission set, which is fail-closed: the holder falls back to an ordinary member.
The anti-escalation invariant
One set-inclusion rule protects the whole model: you can only define, edit, delete or assign a role whose permissions are all included in your own.
- Creating or updating a role whose permissions exceed yours is refused.
- Editing is symmetric: you cannot rewrite (or delete) a role whose current permissions exceed yours, so a limited
roles:manageholder cannot sabotage a more powerful role. - Assigning follows the same inclusion rule, and the system role
ownercan only be granted by an owner.
The consequence: no sequence of operations can ever hand anyone a permission the acting administrator does not already hold.
The console follows effective permissions
GET /v1/admin/context returns the caller's role, effective permissions and tenant. The console uses it to decide which sections to display; enforcement always remains server-side.
curl -sS https://accounts.obexal.com/v1/admin/context \
-H "Authorization: Bearer $OBEXAL_API_TOKEN"
# {"role": "support", "permissions": ["users:view", "audit:view"], "tenant": {...}}Manage roles over the API
All four endpoints require the roles:manage permission, with an Admin API token (Authorization: Bearer obx_...). A custom domain replaces accounts.obexal.com.
| Method and path | Effect |
|---|---|
GET /v1/admin/roles | List custom roles plus the permission catalog |
POST /v1/admin/roles | Create a role |
PUT /v1/admin/roles/{key} | Update name and permissions |
DELETE /v1/admin/roles/{key} | Delete the role and strip it from its holders |
curl -sS -X POST https://accounts.obexal.com/v1/admin/roles \
-H "Authorization: Bearer $OBEXAL_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "support", "name": "Level 1 support", "permissions": ["users:view", "audit:view"]}'
# 201 {"role": {"key": "support", "name": "Level 1 support", "permissions": ["users:view", "audit:view"]}}Deleting a role also removes it from every member who held it: they become ordinary members, so no orphaned key survives (recreating the same key later cannot silently re-grant power to former holders). The number of demoted members is recorded in the audit log, as is every role creation, update and deletion. Custom roles are also part of the declarative bundle, see Policy as code.
Roles stay out of tokens
Roles govern the console and the Admin API only. They never travel in the tokens issued to your applications: ID tokens and access tokens carry the groups claim (the user's group names), not roles. To drive authorization inside your own applications, use groups and app access.