Role-Based Access Control
Users get roles, roles get permissions — simple to reason about, easy to audit, and prone to a predictable failure when the role set grows to match every feature.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
User → role → permissions
The indirection is the point. Assigning billing:refund directly to forty people is unauditable; assigning it to the Support Lead role and assigning that role to forty people is a list a reviewer can read. Permissions should be named as verb-on-resource-type (user:delete, ticket:update) so that the role definition is itself documentation.
Keep the number of roles small and the number of permissions large. Roles are job functions; permissions are capabilities. A system with sixty roles has usually started using roles as permissions.
Admin → user:read user:delete billing:refund settings:write audit:read Support → user:read ticket:update ticket:read Analyst → report:read Member → (own resources only — see ownership; RBAC says nothing about *which* ticket)
Role explosion and what RBAC cannot express
RBAC decides by job, not by record. "Support can update tickets" cannot say "only tickets in their region" or "only tickets assigned to them". Teams respond by minting Support-EU, Support-EU-Enterprise, and so on, until roles outnumber users. The fix is not more roles but a second model: ownership checks for records, ABAC for contextual rules (ABAC and Policy-Based Authorization).
The other failure is privilege accretion. Roles gain permissions during incidents and never lose them, so the Support role ends up able to issue refunds. Review role definitions on a schedule and diff them; a role diff should be a reviewed change like any code.
1// Bad: logic branches on role names; every new role edits every handler2if (user.role === 'admin' || user.role === 'support-lead') refund(order)3 4// Good: handlers ask for a capability; roles map to capabilities in one place5if (!can(principal, 'billing:refund')) return forbidden()6refund(order) // still requires: order is in principal.tenant (tenancy layer)Key points
- Few roles, many permissions; roles are job functions.
- Code checks permissions, never role names.
- RBAC cannot express per-record rules; pair it with ownership or policy.
- Role definitions accrete privilege — review and diff them.
Boundary control exercise
This lesson uses the shared boundary-control exercise.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → obtain a role via a mass-assignment field, a self-service admin flag, or an over-permissive default role.
- 2Role → exercise every permission it has accumulated.
- Privilege elevation to an admin-equivalent role is total compromise of the tenant or system.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Role assignment is a privileged, audited operation with step-up.
- • Default role is minimal.
- • Permission checks centralised.
- • Alert on role assignments, especially self-assignment and admin roles.
- • Diff role definitions on change.
- • Revoke the role, audit actions taken under it, review how it was granted.
- • Roles remain coarse; a legitimate role holder can misuse it fully.