How requireRole Composes with requirePlatformAdmin for RBAC in DeskcommCRM

In DeskcommCRM, requireRole and requirePlatformAdmin form a two-layer RBAC matrix where the tenant-level guard enforces organization-specific role ranks via database lookups while optionally allowing platform admins to bypass rank checks using the allowPlatformAdmin flag, and the platform-level guard validates global super-admin status and MFA compliance before any cross-tenant operations proceed.

The DeskcommCRM back-end protects every /api/v1/* endpoint with a layered authorization strategy that separates tenant-specific permissions from platform-wide administrative privileges. Understanding how requireRole in lib/auth/require-role.ts composes with requirePlatformAdmin is essential for implementing secure, auditable role-based access control across multi-tenant operations.

The Two-Layer RBAC Architecture

The RBAC matrix is implemented through two distinct authorization layers:

  • Tenant (Organization) Level: Enforces the user’s effective role inside the active organization using requireRole(minRole, { allowPlatformAdmin? }) in lib/auth/require-role.ts. This guard queries the database to determine rank and blocks insufficient privileges.

  • Platform (Super-Admin) Level: Validates that the caller is a cross-tenant super-user with the requirePlatformAdmin() guard in lib/auth/requirePlatformAdmin.ts. This layer ensures MFA AAL2 compliance before allowing global administrative actions.

Tenant-Scoped Authorization with requireRole

The requireRole function implements a strict, database-driven permission check. According to the source in lib/auth/require-role.ts (lines 55-90), the execution flow follows these steps:

  1. Load Authentication: Retrieve the authenticated user via loadAuthUser.
  2. Resolve Organization: Determine the active organization through resolveActiveOrg, or look up the specific organizationId from the user’s memberships if provided.
  3. Platform Admin Bypass: If the allowPlatformAdmin option is true and the user is a platform admin and the request is not a support-session, the guard short-circuits and returns success (ok: true).
  4. Database Role Lookup: Query the effective role via the PostgreSQL RPC fn_user_role_in_org.
  5. Rank Comparison: Compare the numeric rank from ROLE_RANK against the requested min parameter. If the rank is insufficient, the guard emits an audit entry and returns a 403 response with code forbidden_role.

Between lines 15-35, the guard enforces MFA requirements. Even if the user possesses adequate rank, failing the MFA check results in a 403 with mfa_required. Notably, the role is always fetched from the database, never from the JWT or cookie snapshot, preventing stale permission exploits.

Global Super-Admin Validation with requirePlatformAdmin

The requirePlatformAdmin guard in lib/auth/requirePlatformAdmin.ts (lines 34-70) establishes the global super-admin barrier:

  1. User Verification: Retrieve the session via supabase.auth.getUser.
  2. Admin Table Check: Verify the existence of an active row in the RLS-protected platform_admins table.
  3. MFA Enforcement: If the admin’s mfa_required metadata flag is true, validate MFA AAL2 using supabase.auth.mfa.getAuthenticatorAssuranceLevel.
  4. Failure Handling: On any validation failure, the guard redirects the user to the appropriate login or forbidden page rather than returning a JSON error.

On success, the function returns a PlatformAdminContext containing the raw Supabase User object and the platformAdmin metadata, enabling subsequent operations to identify the acting super-admin.

Composition Patterns for the RBAC Matrix

The two guards are designed to interoperate rather than conflict, creating three distinct authorization patterns:

1. Admin-Only Routes

Routes under /admin/* invoke requirePlatformAdmin() first. For example, in app/api/v1/admin/users/route.ts, the guard validates global admin status and MFA before processing user management operations.

2. Tenant Operations Within Admin Context

When a platform admin performs actions on a specific tenant (such as impersonation), the code first calls requirePlatformAdmin(), then invokes requireRole with allowPlatformAdmin: true:

// app/api/v1/admin/tenants/[id]/impersonate/route.ts
import { requirePlatformAdmin } from "@/lib/auth/requirePlatformAdmin";
import { requireRole } from "@/lib/auth/require-role";

export async function POST(req: Request, { params }: { params: { id: string } }) {
  const adminCtx = await requirePlatformAdmin(); // Validates global admin + MFA

  // Bypass tenant rank check for platform admins
  const authz = await requireRole("admin", {
    organizationId: params.id,
    allowPlatformAdmin: true,
  });
  if (!authz.ok) return authz.response;

  // Perform impersonation logic using adminCtx.user
}

3. Pure-Tenant Routes

Standard organization endpoints call requireRole without the bypass flag. This forces the database rank check via fn_user_role_in_org and ensures only users with appropriate tenant roles proceed:

// app/api/v1/contatos/route.ts
import { requireRole } from "@/lib/auth/require-role";

export async function POST(req: Request) {
  const authz = await requireRole("manager");
  if (!authz.ok) return authz.response;

  // authz.user and authz.org are guaranteed valid
}

Support Session Exclusions

The allowPlatformAdmin bypass explicitly excludes support sessions. When operating in a support session context, platform admins must still satisfy the tenant’s role hierarchy, preventing privilege escalation during customer support scenarios.

Practical Implementation Examples

Protecting Admin Layouts

Server components use requirePlatformAdmin to gate entire UI sections:

// app/admin/layout.tsx
import { requirePlatformAdmin } from "@/lib/auth/requirePlatformAdmin";

export default async function AdminLayout({ children }: { children: React.ReactNode }) {
  await requirePlatformAdmin(); // Redirects on failure
  return <>{children}</>;
}

Hybrid Authorization with Audit Trails

When allowPlatformAdmin enables bypass, requireRole still performs tenant resolution and emits audit entries. This ensures platform admin actions are logged against the target organization while skipping the rank comparison.

Summary

  • requireRole enforces tenant-specific permissions by querying the database via fn_user_role_in_org and comparing against ROLE_RANK, with optional MFA checks.
  • requirePlatformAdmin validates global super-admin status against the platform_admins table and enforces MFA AAL2, redirecting on failure.
  • Composition occurs through the allowPlatformAdmin flag, allowing validated platform admins to bypass tenant rank checks while maintaining audit trails.
  • Support sessions disable the platform admin bypass, ensuring least-privilege during customer assistance.
  • Helper utilities including loadAuthUser and resolveActiveOrg in lib/auth/server.ts provide the foundational session and organization resolution for both guards.

Frequently Asked Questions

How does the allowPlatformAdmin flag change requireRole behavior?

When allowPlatformAdmin is set to true in the options object, requireRole checks if the user is a platform admin before querying the database for the tenant role. If the user is a global admin and not in a support session, the function returns immediately with ok: true, bypassing the ROLE_RANK comparison while still resolving the target organization for audit purposes.

When should I use requirePlatformAdmin versus requireRole?

Use requirePlatformAdmin for routes that manage global system settings, cross-tenant user administration, or platform-wide configurations. Use requireRole for organization-scoped operations such as managing contacts, deals, or internal tenant users. When building admin tools that manipulate specific tenant data, compose both guards by calling requirePlatformAdmin first, then requireRole with allowPlatformAdmin: true.

How is MFA enforced across both authorization layers?

MFA enforcement happens independently in each layer. requirePlatformAdmin checks for MFA AAL2 compliance specifically for platform administrators using supabase.auth.mfa.getAuthenticatorAssuranceLevel. Separately, requireRole (lines 15-35) validates MFA status for tenant users after the rank check but before allowing the request to proceed, returning mfa_required if the assurance level is insufficient.

What prevents platform admins from bypassing roles during support sessions?

The bypass logic in lib/auth/require-role.ts explicitly checks that the request is not a support-session before allowing platform admins to skip the rank check. This ensures that when admins use support-session functionality to assist customers, they operate under the same ROLE_RANK constraints as the customer’s organization members, maintaining security boundaries during troubleshooting.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →