Obexal Docs

Docs/Access control/Roles and RBAC

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:

RoleMeaning
ownerFull control, including assigning or revoking the owner role
adminDay-to-day administration, all catalog permissions
memberOrdinary 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.

KeyGrants
users:viewView the user directory
apps:manageCreate, edit and delete OAuth applications
audit:viewRead the audit log
members:manageAssign and revoke member roles
tenant:manageTenant settings, policies, SSO, domains, branding
roles:manageDefine custom roles
groups:manageManage 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:manage holder cannot sabotage a more powerful role.
  • Assigning follows the same inclusion rule, and the system role owner can 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 pathEffect
GET /v1/admin/rolesList custom roles plus the permission catalog
POST /v1/admin/rolesCreate 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.