RBAC

RBAC

RBAC security

RBAC

Definition

Role-Based Access Control (RBAC) is an authorization model that restricts system access based on roles assigned to users rather than granting permissions directly to individual accounts. First formalized by David Ferraiolo and Richard Kuhn at NIST in 1992, RBAC introduces a layer of indirection between users and permissions: users are assigned one or more roles, and each role carries a defined set of permissions. This indirection dramatically simplifies access management in systems with many users and fine-grained permissions, because administrators manage a relatively small number of role definitions rather than per-user permission matrices.

RBAC follows the principle of least privilege by default -- users receive only the permissions their assigned roles require and nothing more. The model supports role hierarchies, where senior roles inherit permissions from junior roles; separation of duties, which prevents a single user from accumulating conflicting privileges; and permission inheritance, which enables DRY role composition. These properties make RBAC suitable for both simple applications with a handful of roles and complex enterprise systems requiring hundreds of permission combinations.

In the context of modern web applications built with frameworks like Phoenix, RBAC is typically enforced at the request-handling layer through middleware or plugs that inspect the authenticated user's role before granting access to protected resources. When combined with token-based authentication such as JWT, RBAC claims can be embedded directly in the authentication token, enabling stateless authorization decisions without database lookups on every request.

Core RBAC Model

The NIST RBAC standard (ANSI/INCITS 359-2004) defines four progressive models that build upon each other, providing increasing levels of access control sophistication.

ModelNameDescription
RBAC0Core RBACUsers, roles, permissions, sessions. Minimum viable RBAC.
RBAC1Hierarchical RBACAdds role hierarchies (general and limited). Senior roles inherit junior permissions.
RBAC2Constrained RBACAdds separation of duty constraints (static and dynamic).
RBAC3Symmetric RBACCombines RBAC1 + RBAC2. Full hierarchies with full constraints.

The fundamental relationships in RBAC form a directed graph where permissions flow from roles to users through explicit assignment.

Users ---[assignment]---> Roles ---[grant]---> Permissions ---[target]---> Resources
  |                         |
  |                    [hierarchy]
  |                         |
  v                         v
Sessions              Parent Roles

A user activates a subset of their assigned roles within a session. Permissions are never assigned directly to users; they flow exclusively through role membership. This constraint is what distinguishes RBAC from discretionary access control (DAC) and provides its administrative scalability.

Role Hierarchy and Inheritance

Role hierarchies model organizational structures where senior positions inherit the capabilities of junior positions. In a security platform like Prismatic, the hierarchy reflects the operational authority structure.

RoleInherits FromUnique Permissions
AdminOperator, Analyst, ViewerSystem configuration, user management, role assignment
OperatorAnalyst, ViewerOSINT scan execution, asset modification, compliance actions
AnalystViewerReport generation, risk assessment, alert triage
Viewer(none)Read-only access to dashboards, assets, reports

Hierarchical inheritance is transitive: if Admin inherits from Operator and Operator inherits from Analyst, then Admin automatically holds all Analyst permissions without explicit assignment. This transitivity reduces administrative burden but requires careful design to prevent unintended privilege accumulation.

Two forms of hierarchy exist in the NIST model:

  • General hierarchy: Supports multiple inheritance. A role can inherit from many parent roles, forming a directed acyclic graph (DAG). More flexible but harder to reason about.
  • Limited hierarchy: Restricts inheritance to a single parent, forming a tree. Simpler to reason about but less flexible for complex organizational structures.

Permission Model

Permissions in RBAC are defined as operations on resources. A permission typically consists of three components that together specify exactly what action is allowed on which entity within what scope.

ComponentDescriptionExample
ResourceThe protected entityassets, reports, scans, users
OperationThe action being performedread, create, update, delete, execute
ScopeOptional constraint on which instancesown, team, organization, all

A complete permission might be expressed as assets:read:all (read any asset) or scans:execute:team (execute scans within the user's team scope). This three-part structure enables fine-grained access control without combinatorial explosion.

defmodule PrismaticWeb.Permissions do
  @moduledoc """
  Permission resolution engine for RBAC authorization.
  Resolves user permissions through role hierarchy traversal,
  supporting the three-part permission model (resource:operation:scope).
  """

  @type resource :: :assets | :reports | :scans | :users | :compliance | :agents
  @type operation :: :read | :create | :update | :delete | :execute
  @type scope :: :own | :team | :organization | :all

  @type permission :: {resource(), operation(), scope()}

  @spec has_permission?(User.t(), permission()) :: boolean()
  def has_permission?(%User{roles: roles}, permission) do
    roles
    |> Enum.flat_map(&resolve_permissions/1)
    |> Enum.any?(&permission_matches?(&1, permission))
  end

  @spec resolve_permissions(Role.t()) :: list(permission())
  defp resolve_permissions(role) do
    role
    |> expand_hierarchy()
    |> Enum.flat_map(& &1.permissions)
    |> Enum.uniq()
  end

  @spec permission_matches?(permission(), permission()) :: boolean()
  defp permission_matches?({r, o, :all}, {r, o, _scope}), do: true
  defp permission_matches?({r, o, s}, {r, o, s}), do: true
  defp permission_matches?(_granted, _required), do: false

  defp expand_hierarchy(role) do
    [role | Enum.flat_map(role.parents || [], &expand_hierarchy/1)]
  end
end

Separation of Duties

Separation of duties (SoD) is a critical RBAC constraint that prevents conflicts of interest by ensuring that no single user can complete a sensitive workflow alone. RBAC2 and RBAC3 support two forms of separation.

Static Separation of Duties (SSoD): Enforced at role assignment time. A user cannot be assigned to mutually exclusive roles. For example, a user cannot simultaneously hold both "Auditor" and "Operator" roles, because an operator should not audit their own actions.

Dynamic Separation of Duties (DSoD): Enforced at session activation time. A user may hold conflicting roles but cannot activate them in the same session. This is more flexible than SSoD, allowing users to switch contexts while maintaining separation.

SoD TypeEnforcement PointExamplePrismatic Application
StaticRole assignmentAuditor and Operator mutually exclusiveRed Team and Blue Team role separation
DynamicSession activationCannot activate Admin and Viewer simultaneouslyCannot run OSINT scan and approve compliance in same session
defmodule PrismaticWeb.SeparationOfDuties do
  @moduledoc """
  Enforces static and dynamic separation of duty constraints.
  Prevents conflicting role assignments and simultaneous activation
  of mutually exclusive roles within a single session.
  """

  @mutually_exclusive_roles [
    {:auditor, :operator},
    {:red_team, :blue_team},
    {:compliance_approver, :compliance_submitter}
  ]

  @spec validate_assignment(User.t(), atom()) :: :ok | {:error, :sod_violation}
  def validate_assignment(%User{roles: current_roles}, new_role) do
    conflicting =
      @mutually_exclusive_roles
      |> Enum.filter(fn {a, b} ->
        (a == new_role and b in current_roles) or
        (b == new_role and a in current_roles)
      end)

    case conflicting do
      [] -> :ok
      [{a, b} | _] -> {:error, {:sod_violation, :static, a, b}}
    end
  end

  @spec validate_session_activation(list(atom()), atom()) :: :ok | {:error, :sod_violation}
  def validate_session_activation(active_roles, role_to_activate) do
    conflicting =
      @mutually_exclusive_roles
      |> Enum.filter(fn {a, b} ->
        (a == role_to_activate and b in active_roles) or
        (b == role_to_activate and a in active_roles)
      end)

    case conflicting do
      [] -> :ok
      [{a, b} | _] -> {:error, {:sod_violation, :dynamic, a, b}}
    end
  end
end

Plug-Based RBAC Enforcement

The Prismatic Platform implements RBAC enforcement in the Phoenix request pipeline using Plug middleware. This ensures that authorization checks occur before any controller logic executes.

defmodule PrismaticWeb.Plugs.RequireRole do
  @moduledoc """
  Plug that enforces role-based access control on HTTP requests.
  Checks the authenticated user's roles against the required role
  for the endpoint, supporting hierarchical role inheritance.
  """

  import Plug.Conn

  @spec init(keyword()) :: keyword()
  def init(opts), do: opts

  @spec call(Plug.Conn.t(), keyword()) :: Plug.Conn.t()
  def call(conn, required_role: role) do
    case conn.assigns[:current_user] do
      %{roles: roles} when is_list(roles) ->
        if role_satisfied?(roles, role) do
          conn
        else
          conn
          |> put_status(:forbidden)
          |> Phoenix.Controller.json(%{error: "Insufficient permissions", required_role: role})
          |> halt()
        end

      _ ->
        conn
        |> put_status(:unauthorized)
        |> Phoenix.Controller.json(%{error: "Authentication required"})
        |> halt()
    end
  end

  @spec role_satisfied?(list(atom()), atom()) :: boolean()
  defp role_satisfied?(user_roles, required_role) do
    Enum.any?(user_roles, fn role ->
      role == required_role or inherits_from?(role, required_role)
    end)
  end

  @spec inherits_from?(atom(), atom()) :: boolean()
  defp inherits_from?(:admin, _role), do: true
  defp inherits_from?(:operator, role) when role in [:analyst, :viewer], do: true
  defp inherits_from?(:analyst, :viewer), do: true
  defp inherits_from?(_role, _required), do: false
end

Guardian and JWT Integration

The Prismatic Platform uses the Guardian library for JWT-based authentication with RBAC claims embedded in tokens. When a user authenticates, their role assignments are serialized into the JWT payload alongside standard claims, enabling stateless authorization decisions.

JWT ClaimPurposeExample Value
subSubject (user identifier)"user:42"
rolesAssigned role list["analyst", "viewer"]
permissionsExplicit permission overrides["assets:read:all"]
teamTeam membership for scoping"blue-team"
expToken expiration timestamp1706745600

Token refresh preserves role information, and role changes take effect at next token issuance. For immediate role revocation (security incidents), the platform maintains a token revocation list checked at the Gateway level, trading pure statelessness for security responsiveness.

Agent Authority System

Beyond traditional user RBAC, the AIAD agent level system (L1 through L5) functions as a parallel RBAC hierarchy for autonomous agents. This dual RBAC model -- one for human users, one for AI agents -- is a distinguishing architectural feature of the Prismatic Platform.

Agent LevelAuthorityEquivalent User RoleCapabilities
L1Strategic SupremeAdminFull platform authority, cross-domain operations
L2TacticalOperatorDomain-specific operations, resource allocation
L3Strategic CommanderOperatorTeam coordination, multi-agent orchestration
L4Operational SpecialistAnalystSpecialized analysis, focused operations
L5WorkerViewerData collection, basic processing

Higher-level agents possess broader operational authority: an L3 Strategic Commander can override decisions made by L4 Operational Specialists within their domain. The agent authority system enforces the same principles as user RBAC -- least privilege, separation of duties, audit logging -- but adapted for autonomous operation.

Color Team RBAC

The Color Teams security framework leverages RBAC principles to enforce operational boundaries between adversarial and defensive teams.

TeamPermission SetRestrictions
Red TeamAdversarial simulation, synthetic attack generationNo production data access, sandboxed execution only
Blue TeamDefensive monitoring, signal aggregationRead-only monitoring, no offensive operations
Purple TeamCross-team visibility, synthesisCannot directly execute adversarial operations
White TeamFormal verification, proof constructionRead-only analysis, no system modification
Black TeamAbstract threat modelingMaximum isolation, no executable output
Gray TeamBoundary exploration, edge case discoveryRead-only, zero state changes

This team-based RBAC ensures that the Red Team cannot accidentally (or intentionally) access production systems, while the Blue Team cannot interfere with adversarial simulations. The Purple Team coordinator has cross-team visibility but cannot directly execute operations in either domain, enforcing separation of duties at the organizational level.

RBAC vs. Alternative Models

ModelApproachStrengthsWeaknessesPrismatic Usage
RBACRole-basedScalable, auditable, separation of dutiesCan become complex with many rolesPrimary authorization model
ABACAttribute-basedContext-aware, fine-grainedComplex policy managementSupplementary for OSINT scoping
DACOwner-basedSimple, flexibleNo central control, privilege creepNot used
MACLabel-basedStrong isolation, military-gradeInflexible, high admin overheadBlack Team isolation
ACLPer-resource listsIntuitive, per-objectDoes not scale, no hierarchyFile-level permissions only
ReBACRelationship-basedModels real-world relationshipsComplex graph evaluationUnder evaluation for KuzuDB

Prismatic uses RBAC as the primary model, supplemented by attribute-based checks for context-sensitive decisions like OSINT data access scoping (e.g., "Analysts can only view assets within their assigned geographical region").

Audit and Compliance

RBAC provides strong auditability properties that support compliance with regulations like GDPR, NIS2, and ISO 27001. Every authorization decision can be traced through the role hierarchy to the specific permission grant.

defmodule PrismaticWeb.AuthorizationAudit do
  @moduledoc """
  Immutable audit logging for all authorization decisions.
  Captures the full context of each access decision including
  user, role, permission, resource, and outcome.
  """

  require Logger

  @spec log_decision(map()) :: :ok
  def log_decision(%{user_id: user_id, roles: roles, permission: permission,
                     resource: resource, outcome: outcome}) do
    Logger.info("Authorization decision",
      user_id: user_id,
      roles: inspect(roles),
      permission: inspect(permission),
      resource: resource,
      outcome: outcome,
      timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
    )
  end
end

Common Anti-Patterns

Anti-PatternProblemMitigation
Role explosionCreating a unique role for every permission combinationUse permission composition and hierarchy instead
God roleA single "admin" role with all permissionsDefine granular admin sub-roles
Permission creepAccumulating roles over time without reviewImplement periodic access reviews
Hardcoded rolesEmbedding role names in application logicUse permission checks, not role name checks
Missing auditNo logging of role changes or access decisionsLog all authorization events with context
Role-permission couplingDefining roles and permissions in the same moduleSeparate permission definitions from role assignments
Implicit deny failureFailing to enforce deny-by-defaultAlways deny unless explicitly permitted

Security Considerations

RBAC implementation requires attention to several security concerns that go beyond the authorization model itself.

  • Least privilege enforcement: Roles should be defined with minimum necessary permissions. Regular audits should identify and remove unused permission grants.
  • Role assignment governance: Role assignment should require approval workflows, especially for privileged roles. The Prismatic Platform logs all role changes with immutable audit trails.
  • Token-based RBAC caveats: When roles are embedded in JWTs, changes do not take effect until token refresh. Critical role revocations need a server-side revocation mechanism.
  • Cross-system consistency: In distributed systems, RBAC policies must be consistent across all services. The Prismatic API Gateway serves as the single enforcement point, preventing policy drift.
  • Encryption at rest: Role definitions and user-role mappings stored in PostgreSQL should be protected by encryption at rest to prevent unauthorized access to authorization metadata.
  • Session management: Token expiration and refresh cycles must balance security (short-lived tokens) with usability (not requiring frequent re-authentication).

Best Practices

Use permission checks, not role checks: Application code should check whether the user has a specific permission (e.g., has_permission?(user, {:assets, :read, :all})) rather than checking for a specific role name (e.g., user.role == :admin). This decouples business logic from role definitions and makes role restructuring safe.

Define roles around job functions: Roles should correspond to organizational job functions (Analyst, Operator, Admin) rather than technical capabilities. This makes role assignment intuitive and reduces the need for per-user permission overrides.

Implement periodic access reviews: Schedule quarterly or semi-annual reviews of role assignments to identify and remove stale permissions. Users who change positions or leave the organization should have their roles updated promptly.

Enforce deny-by-default: Every access check should default to denial. Access is granted only when an explicit permission match is found through the role hierarchy. This prevents accidental access from missing role definitions.

Log all authorization decisions: Every access decision -- both grants and denials -- should be logged with full context (user, role, permission, resource, timestamp). These logs are essential for incident investigation and compliance auditing.

  • JWT - Token format carrying RBAC claims for stateless authorization
  • OAuth2 - Authorization framework mapping scopes to RBAC roles
  • API Gateway - Centralized enforcement point for RBAC policies
  • Plug - Elixir middleware implementing RBAC checks in the request pipeline
  • Encryption at Rest - Protects stored role and permission data
  • Agent Tier - AIAD agent authority levels functioning as agent RBAC
  • Color Teams - Security teams with role-based operational authority
  • Rate Limiting - Per-role rate limit policies at the API gateway
  • TLS - Transport encryption protecting RBAC token exchange
  • Observability - Monitoring and auditing authorization decisions
  • GDPR - Regulation requiring access control for personal data

See Also

  • Architecture - Platform security architecture
  • Apps - Prismatic API and Web applications
  • OSINT - OSINT access control and data scoping

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL