Platform Architecture Modules Developers Security Integrations Articles
Articles / Permissions & Security
Permissions & Security 3 min read Published Mar 9, 2026

RBAC Explained: Designing Permissions for Complex Business Systems

A definitive guide to implementing scalable Role-Based Access Control (RBAC): hierarchy trees, scoping, caching strategies, and common anti-patterns.

A
Alegor Architecture Team
Platform Engineering & Architecture

Authorization is frequently trivialized during the prototype stage as a simple role string column on the users table ('admin', 'editor', 'viewer'). However, as business software matures into multi-tenant operations, multi-department hierarchies, and external collaborator access, naive authorization breaks down completely.

A production-grade Role-Based Access Control (RBAC) architecture separates Users, Roles, Permissions, and Scopes into an explicit, deterministic model.

+----------+          +---------------+          +------------------+          +------------------+
|   USER   | -------- |  MEMBERSHIP   | -------- |       ROLE       | -------- |    PERMISSION    |
| (Entity) |          | (Tenant Link) |          | (Admin, Auditor) |          | (invoices.issue) |
+----------+          +---------------+          +------------------+          +------------------+
                             |
                             v
                      +---------------+
                      |     SCOPE     |
                      | (Dept / Org)  |
                      +---------------+

The Four Core Elements of Scalable RBAC#

1. Atomic Permissions (Capabilities)#

Permissions represent discrete, immutable actions on specific resources:

  • invoices.view
  • invoices.create
  • invoices.issue
  • invoices.void
  • settings.billing.manage

Core Rule: Application controllers and UI components must never check for roles directly. They must exclusively check for atomic permissions:

// ANTI-PATTERN: Checking role names tightly couples UI/controllers to arbitrary titles
if ($user->role === 'accountant') { ... }

// BEST PRACTICE: Checking atomic capabilities preserves custom role flexibility
if ($user->can('invoices.issue', $invoice)) { ... }

2. Composable Roles#

Roles are named collections of permissions (e.g., Billing Specialist, Regional Auditor, Support Read-Only). In multi-tenant environments, default roles are provided out of the box, while enterprise tenants have the freedom to create custom roles tailored to their internal division of labor.

3. Hierarchical Inheritance#

Roles can inherit permissions from subordinate roles. For example:

  • Owner inherits Organization Admin
  • Organization Admin inherits Department Manager
  • Department Manager inherits Standard Member

This prevents administrative redundancy when new permissions are introduced into the platform.

4. Contextual Scopes (Resource Constraints)#

A user might be an Editor strictly within the EMEA Logistics department, but only a Viewer across the broader global organization. Scoped role bindings associate a user, a role, and a specific resource hierarchy.

High-Performance Caching & Invalidation#

Executing multiple recursive SQL joins on every HTTP request to resolve permissions introduces significant latency. A scalable authorization engine implements sub-millisecond evaluation via Redis / in-memory bitmasks:

class PermissionRegistrar
{
    public function getCachedPermissions(User $user, Tenant $tenant): array
    {
        $cacheKey = "auth:tenant_{$tenant->id}:user_{$user->id}:permissions";

        return Cache::remember($cacheKey, now()->addHours(6), function () use ($user, $tenant) {
            return $user->roles()
                ->where('tenant_id', $tenant->id)
                ->with('permissions')
                ->get()
                ->flatMap(fn ($role) => $role->permissions->pluck('name'))
                ->unique()
                ->values()
                ->all();
        });
    }

    public function invalidate(User $user, Tenant $tenant): void
    {
        Cache::forget("auth:tenant_{$tenant->id}:user_{$user->id}:permissions");
    }
}

Common RBAC Anti-Patterns#

  1. Hardcoding Role Enums in Controllers: Eliminates customer ability to define custom roles.
  2. Infinite Role Proliferation: Creating separate roles for every slight variation (Accountant_EU, Accountant_US_Read) instead of utilizing scoping attributes.
  3. Implicit Default-Allow: Authorization gates must default to Deny. If a permission is missing or evaluation encounters an error, access is rejected immediately.

Explore the Alegor Permissions Module or read our comparison of RBAC vs ABAC.

Building a business-critical system?

Evaluate how Alegor can serve as your foundation.

Explore Platform →