# How DeskcommCRM Implements Role-Based Access Control (RBAC)

> Discover how DeskcommCRM implements Role-Based Access Control RBAC with a centralized guard token validation tenant context role fetching Supabase RPC and MFA checks.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: how-to-guide
- Published: 2026-09-12

---

**DeskcommCRM enforces Role-Based Access Control (RBAC) through a centralized `requireRole` guard in [`lib/auth/require-role.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/require-role.ts) that validates JWT tokens, resolves tenant context, fetches effective roles from a Supabase RPC, and enforces numeric rank comparisons with mandatory MFA checks.**

DeskcommCRM is an open-source customer relationship management platform built on Next.js and Supabase. Its authorization architecture relies on a strict, auditable **Role-Based Access Control (RBAC)** system that centralizes permission logic in a single validation layer. Every protected API route invokes this guard to ensure users possess the minimum required privileges before accessing sensitive data or operations.

## The Central Authorization Guard: `requireRole`

The heart of DeskcommCRM's RBAC implementation lives in [`lib/auth/require-role.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/require-role.ts). This module exports the `requireRole` function, which serves as the exclusive entry point for access control decisions across the entire application. Rather than scattering permission checks throughout business logic, the platform consolidates all authorization concerns—including authentication verification, tenant resolution, role fetching, and multi-factor authentication (MFA) validation—within this single utility.

### The Eight-Step Authorization Flow

When an API route invokes `requireRole`, the function executes a rigorous, sequential validation pipeline:

1. **Authentication Verification** – The `loadAuthUser()` helper validates the JWT token using Supabase's `auth.getUser()` method (never `getSession()`). Invalid or missing tokens immediately return a **401 Unauthenticated** response.

2. **Tenant Resolution** – `resolveActiveOrg(user)` extracts the active organization from a trusted, cookie-backed membership list. If no valid tenant context exists, the guard returns a **403 Forbidden-Tenant** error.

3. **Effective Role Retrieval** – The system queries the database RPC `fn_user_role_in_org(p_org)`, a PostgreSQL function marked as **SECURITY DEFINER** that serves as the single source of truth for role data. This function is used by both the RBAC guard and Row Level Security (RLS) policies.

4. **Rank Translation** – The `ROLE_RANK` mapping (defined in [`lib/auth/types.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/types.ts)) converts role strings (e.g., `admin`, `manager`, `viewer`) into numeric values, enabling simple mathematical privilege comparisons.

5. **Platform-Admin Bypass** – If the `allowPlatformAdmin` option is enabled and `user.is_platform_admin` is true, the function skips standard rank checks, granting cross-tenant superuser access.

6. **MFA Enforcement** – Before final authorization, `await mfaEmDivida()` verifies multi-factor authentication requirements. Missing MFA triggers a **403 MFA-Required** error and logs an audit event.

7. **Privilege Comparison** – The guard compares the user's effective rank against `ROLE_RANK[min]`. If the user's rank is lower, `fail("forbidden_role")` from [`lib/api/wrappers.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/api/wrappers.ts) returns a structured **403** response and emits an `authz.denied` audit entry.

8. **Success Resolution** – Upon passing all checks, the function returns `{ ok: true, user, org }`, where `org.role` contains the effective role fetched directly from the database.

## Role Hierarchy and Rank-Based Comparisons

DeskcommCRM eschews complex permission matrices in favor of a **rank-based hierarchy** defined in [`lib/auth/types.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/types.ts). The `ROLE_RANK` constant maps each role to a numeric value (e.g., `admin = 3`, `manager = 2`, `viewer = 1`). This design allows the authorization logic to use simple "greater-than-or-equal" comparisons rather than bitwise operations or complex set intersections.

This approach ensures that higher-privileged roles automatically inherit permissions granted to lower ranks, simplifying both the mental model and the implementation code.

## Multi-Factor Authentication Integration

Security hardening extends beyond role verification. The `requireRole` guard enforces **MFA-first policies** by validating multi-factor authentication before evaluating role ranks. This sequencing prevents role information leakage to unauthenticated sessions and ensures that privilege escalation attempts are blocked at the authentication layer.

The `mfaEmDivida()` check integrates with the platform's audit system, logging denied attempts asynchronously to avoid impacting response latency while maintaining comprehensive security trails.

## Platform Administration Overrides

For global system administration, DeskcommCRM supports a **platform-admin bypass** mechanism. When `requireRole` is called with `allowPlatformAdmin: true`, the guard checks the `user.is_platform_admin` boolean flag. Platform administrators bypass tenant-specific rank checks, enabling them to access organization-scoped endpoints without traditional membership constraints.

This capability is critical for support operations and global metrics collection while maintaining strict separation between standard user authorization and superuser privileges.

## Usage Examples

### Protecting Standard API Routes

API routes import the guard and specify the minimum required role. The function returns a standardized response object that routes can immediately return to clients:

```typescript
// app/api/v1/customers/create.ts
import { requireRole } from "@/lib/auth/require-role";
import { ok } from "@/lib/api/wrappers";

export async function POST(req: Request) {
  // Require at least "manager" role for this endpoint
  const authz = await requireRole("manager", { 
    requestId: req.headers.get("x-request-id") 
  });
  
  if (!authz.ok) return authz.response;   // 401 / 403 automatically handled

  // ...handle the POST body, perform DB writes, etc.
  return ok({ message: "Cliente criado com sucesso." });
}

```

### Leveraging Platform-Admin Privileges

Global endpoints utilize the bypass option to grant access to platform administrators:

```typescript
// app/api/v1/platform/metrics.ts
import { requireRole } from "@/lib/auth/require-role";

export async function GET(req: Request) {
  // Platform admins can access this endpoint without a tenant context
  const authz = await requireRole("viewer", { allowPlatformAdmin: true });
  if (!authz.ok) return authz.response;

  // ...return global metrics
}

```

### Inspecting Authorization Results

Successful authorization returns rich context about the user and their organizational role:

```typescript
const { ok, user, org } = await requireRole("admin");
if (ok) {
  console.log("User ID:", user.id);
  console.log("Active org:", org.orgId);
  console.log("Effective role:", org.role); // role fetched from DB
}

```

## Database-Level Consistency

The RBAC system maintains consistency between application logic and database policies through the `fn_user_role_in_org(p_org)` RPC. Defined as a **SECURITY DEFINER** function in Supabase migrations, this PostgreSQL routine calculates the user's effective role within a specific organization. Because both the `requireRole` guard and Supabase RLS policies invoke this identical function, the platform eliminates authorization drift between application layers and database constraints.

This architectural decision ensures that direct database access (through Supabase clients or SQL) respects the same role hierarchy as the Next.js API routes, creating a unified security perimeter.

## Summary

- **DeskcommCRM centralizes RBAC logic** in the `requireRole` function within [`lib/auth/require-role.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/require-role.ts), ensuring consistent authorization across all API routes.
- **Rank-based hierarchy** via `ROLE_RANK` in [`lib/auth/types.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/types.ts) enables simple numeric comparisons for privilege evaluation.
- **Multi-factor authentication** is enforced before role checks via `mfaEmDivida()`, preventing information leakage and strengthening security posture.
- **Platform administrators** can bypass tenant restrictions using the `allowPlatformAdmin` option for global system management.
- **Database consistency** is achieved by sharing the `fn_user_role_in_org` RPC between the application guard and PostgreSQL RLS policies.
- **Comprehensive auditing** logs all authorization denials through the `fail` wrapper in [`lib/api/wrappers.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/api/wrappers.ts) without blocking response threads.

## Frequently Asked Questions

### How does DeskcommCRM prevent unauthorized access across different organizations?

The `resolveActiveOrg` function in [`lib/auth/server.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/auth/server.ts) extracts the active tenant from a cryptographically signed cookie-backed membership list. If the user lacks a valid membership for the requested organization, the guard returns a **403 Forbidden-Tenant** error before evaluating role permissions. This ensures strict multi-tenancy isolation at the authentication layer.

### Why does the RBAC system use numeric ranks instead of permission lists?

DeskcommCRM implements a rank-based hierarchy through the `ROLE_RANK` mapping to simplify privilege evaluation. By assigning numeric values to roles (e.g., `admin = 3`, `manager = 2`, `viewer = 1`), the system can perform simple "greater-than-or-equal" comparisons rather than complex set intersections or bitwise operations. This approach ensures that higher-privileged roles automatically inherit permissions from lower ranks.

### What happens if a user has the correct role but hasn't completed MFA verification?

The `requireRole` guard executes `await mfaEmDivida()` before evaluating role ranks. If multi-factor authentication is required but not satisfied, the function immediately returns a **403 MFA-Required** error and logs an `authz.denied` audit event. This MFA-first policy ensures that role information is never exposed to sessions lacking proper authentication factors.

### How does the platform ensure that API authorization matches database-level security?

Both the application-layer guard and Supabase Row Level Security (RLS) policies rely on the identical `fn_user_role_in_org(p_org)` RPC function. Defined as a **SECURITY DEFINER** PostgreSQL routine, this function serves as the single source of truth for role data. Because API routes and database policies query the same authoritative source, DeskcommCRM eliminates authorization inconsistencies between the Next.js application and direct database access.