Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

RBAC vs. ABAC: Policy Model Design for Efficient Evaluation

Hello! Welcome back to our course on designing security products.

In our last lesson, we architected advanced session management for our Identity Provider (IdP), covering Single Sign-On, Single Logout, and the use of channel-bound tokens to prevent theft. We now have a robust system for authentication—proving who a user is.

This lesson transitions from authentication to authorization—determining what an authenticated user is allowed to do. This brings us to our next learning outcome: Compare RBAC and ABAC policy models and design their data structures for efficient evaluation.

We'll start with the most common authorization model, Role-Based Access Control (RBAC), and analyze its strengths and weaknesses. We will then explore the more powerful and flexible Attribute-Based Access Control (ABAC) model. For both, we will focus on designing the underlying data structures with a keen eye on the performance of the evaluation logic, a critical consideration when building a real-world authorization service.

1. Role-Based Access Control (RBAC): The Foundation

The simplest way to handle permissions is to scatter if statements checking a user's role throughout your code. As you know from your software engineering experience, this quickly becomes unmaintainable. RBAC provides the first layer of structured abstraction over this chaos.

How To Handle Permissions Like A Senior Dev

To build our first-principles understanding, let's start with the basics. This video from Web Dev Simplified does an excellent job of showing the evolution from messy inline checks to a structured RBAC system.

Watch the segments from the beginning to 00:52 and from 03:01 to 06:23. Pay attention to how RBAC centralizes permission logic by defining permissions as action:resource pairs and grouping them into roles.

As the video demonstrates, the core idea of RBAC is to decouple users from specific permissions using an intermediate layer: roles.

  • Permissions: Granular definitions of what can be done, often as an action:resource pair (e.g., read:document, delete:comment).
  • Roles: Collections of permissions. A viewer role might have read:document, while an editor role has read:document and edit:document.
  • Users: Are assigned one or more roles.

A user's total set of permissions is the union of all permissions granted by their assigned roles.

Designing Data Structures for RBAC

Given your background, you'll immediately recognize this as a classic relational database modeling problem involving many-to-many relationships.

Database Schema for Role-Based Access Control (RBAC)
This Entity-Relationship Diagram (ERD) shows a common database schema for implementing RBAC. It uses junction tables to manage the many-to-many relationships between users, roles, and permissions.

A typical relational schema for RBAC would consist of:

  1. Core Tables:

    • users: Stores user information.
    • roles: Stores role definitions (e.g., id, name).
    • permissions: Stores permission definitions (e.g., id, action, resource).
  2. Junction Tables:

    • user_roles: Maps users to roles (user_id, role_id).
    • role_permissions: Maps roles to permissions (role_id, permission_id).

Efficient Evaluation in RBAC:
To check if a user has a specific permission (e.g., delete:comment):

  1. Find all role_ids for the user_id in the user_roles table.
  2. For that set of role_ids, find all associated permission_ids in the role_permissions table.
  3. Check if the target permission exists in the resulting set of permissions.

In SQL, this is typically a series of JOINs. For performance, a common strategy is to compute and cache a user's full permission set upon login or their first request. This turns subsequent authorization checks into fast in-memory lookups, avoiding repeated database queries.

The Limitations of RBAC

RBAC is simple and effective for many applications, but it breaks down when access rules become more nuanced.

How To Handle Permissions Like A Senior Dev

The Web Dev Simplified video also clearly illustrates the main limitation of RBAC, which motivates the need for a more powerful model.

Watch the segment from 06:23 to 08:26. Notice how trying to implement a rule like 'a user can delete their own comment' makes the RBAC model awkward.

The key problem is that RBAC permissions are static. A role either grants delete:comment or it doesn't. It has no mechanism to consider the attributes of the specific object being accessed (e.g., comment.author_id). Trying to model this in RBAC leads to role explosion, where you create an unmanageable number of highly specific roles like comment-owner, document-editor-in-project-x, etc.

This limitation brings us to our next model: ABAC.

2. Attribute-Based Access Control (ABAC): Dynamic and Fine-Grained

ABAC makes authorization decisions by evaluating policies against attributes of the entities involved in the request. It's a fundamental shift from static roles to dynamic, real-time policy evaluation.

[PDF] Guide to Attribute Based Access Control (ABAC) Definition and ...

For a formal definition, let's turn to the NIST Guide to ABAC. This document provides the standard industry definition and components.

Please read sections '2. Understanding ABAC' (starting on page 4) and '2.3 Basic ABAC Concepts' (starting on page 8). Focus on the definitions of Subject, Object, and Environment attributes and how policies are expressed as rules combining them. The diagrams in this section are very helpful.

As defined by NIST, ABAC policies are rules based on four types of information:

  • Subject Attributes: Properties of the user requesting access (e.g., role, department, security_clearance).
  • Object (or Resource) Attributes: Properties of the resource being accessed (e.g., owner, creation_date, sensitivity_level).
  • Action: The specific operation the subject is trying to perform (e.g., read, write, delete).
  • Environment Attributes: Contextual information about the request (e.g., time_of_day, ip_address, device_security_status).
ABAC vs. RBAC Access Decision Workflows
This flowchart visually contrasts the ABAC and RBAC decision processes. RBAC's path is determined by a static role assignment, while ABAC's path involves a dynamic evaluation of multiple attributes against a policy.

With ABAC, the rule "a user can delete their own comment" becomes simple to express: user.id == resource.author_id AND action == 'delete'. Notice that role can simply be another attribute of the user, meaning ABAC can be used to implement RBAC.

Designing Data Structures for ABAC

Designing for ABAC is less about relational tables and more about how you represent and evaluate policies.

How To Handle Permissions Like A Senior Dev

Let's see a practical, code-centric implementation of an ABAC system. This will give us a concrete idea of the data structures involved.

Watch from 19:16 to 33:19. This is a longer segment, but it's a fantastic walkthrough of building an ABAC system in code. Pay close attention to how the permissions object is structured and how the hasPermission function evaluates rules that are either booleans or functions that take the user and resource as arguments.

Based on the video and ABAC principles, let's outline the data structures.

1. Attribute Storage:
Attributes are key-value pairs associated with entities.

  • Subject Attributes: Often stored on the user object/record itself (e.g., department, job_title columns) or in a related table/document (e.g., a user_attributes JSONB column in PostgreSQL).
  • Object Attributes: Stored on the resource being protected (e.g., a document table would have owner_id, classification columns).

2. Policy Storage:
Policies are the core of ABAC. A policy is essentially a function: f(subject, resource, action, environment) -> (allow | deny).

  • In Code: As shown in the video, policies can be defined directly in code as a nested map where the final value is a boolean or a function that implements the rule logic. This is simple but requires a code deployment to change a policy.
  • In a Database: For dynamic policy management, you can store policies in a database. A policies table might look like this:
    • id: Unique identifier.
    • effect: allow or deny.
    • action: The action this policy applies to (e.g., delete:comment).
    • conditions: A field (e.g., JSONB) storing the logic. For example: {"and": [{"eq": ["subject.id", "resource.author_id"]}]}.
      This policy would then be parsed and evaluated by a Policy Engine.

The Challenge: Efficient Evaluation

The naive way to evaluate ABAC is to retrieve all policies relevant to a requested action and evaluate them one by one. For a system with thousands of policies, this linear scan is a major performance bottleneck. This is where designing for efficient evaluation is critical.

The key idea is to pre-process the policies into a data structure optimized for fast lookups, much like a database uses an index to avoid a full table scan. A tree is a natural fit for this.

PolTree: A Data Structure for Making Efficient Access Decisions in ...

A research paper titled 'PolTree: A Data Structure for Making Efficient Access Decisions' tackles this exact problem. While the paper is academic, its core ideas are highly relevant to our goal of designing a high-performance authorization engine.

Skim Section 4, 'Policy Tree for ABAC'. You don't need to read every line, but focus on understanding the concepts of the two data structures proposed: Binary Policy Tree (B-PolTree): Each node is an attribute-value pair (e.g., Designation=Professor). The 'Yes' branch is taken if the request matches, 'No' if it doesn't. N-ary Policy Tree (N-PolTree): Each node is an attribute (e.g., Designation). It then has a branch for each possible value (Professor, Student, etc.). \nThis provides a brilliant 'first principles' look at how to optimize policy evaluation.

The PolTree paper gives us two excellent architectural patterns for our policy evaluation engine:

  • B-PolTree (Binary Decision Diagram style): We construct a binary tree where each node represents a single condition (attribute=value). When a request comes in, we traverse the tree. If the condition at a node is met, we go left; otherwise, we go right. The leaves of the tree are Allow or Deny. This transforms a list of policies into a single, fast-to-traverse decision diagram.

  • N-PolTree (Decision Tree style): We build a tree where each node represents an attribute (e.g., Department). The branches from that node represent all possible values for that attribute (Sales, HR, Engineering). The evaluation engine traverses the tree by selecting the branch that matches the incoming request's attributes at each level. The construction algorithm cleverly chooses the attribute with the highest entropy at each level to create the most balanced and efficient tree.

You don't need to implement these today, but the architectural takeaway is crucial: to build a high-performance ABAC system, you pre-compile your declarative policies into an optimized data structure (like a tree) that makes runtime evaluation a fast traversal rather than a slow linear scan.

3. A Glimpse Forward: Relationship-Based Access Control (ReBAC)

While ABAC is incredibly powerful, a modern evolution called Relationship-Based Access Control (ReBAC) has emerged for modeling complex sharing scenarios, famously implemented in Google's Zanzibar system.

Google Zanzibar Authorization System: Deep Dive and Implementation Tips

Let's briefly look at ReBAC. It's not strictly ABAC, but it's a logical next step and is often discussed in the same context. This talk on Google Zanzibar provides a great introduction.

Watch the segment from 05:42 to 07:45. The key concept is that access is based on the relationship between subjects and objects, such as 'user A can view file B because they are a viewer of file B's parent folder'.

In ReBAC, policies and data are merged into relationship tuples of the form (user, relation, object).

  • user:anne is a viewer of doc:plan
  • group:eng#member is an editor of folder:secrets

Policies then define how these relationships compose, e.g., viewer = editor or parent.viewer. Evaluation becomes a graph traversal problem. While incredibly scalable, this often requires specialized data stores and is a subject for its own deep dive. We will revisit this when we talk about unified platforms.

Conclusion

In this lesson, we dissected and compared the two primary authorization models, RBAC and ABAC, and designed their core data structures. Your experience as a software engineer is directly applicable to understanding the trade-offs in their implementation.

Key Takeaways:

  • RBAC is simple and great for static, coarse-grained permissions. Its data structure is typically a set of relational tables mapping users to roles and roles to permissions. Its main weakness is role explosion.
  • ABAC is flexible and powerful, enabling dynamic, fine-grained, and context-aware authorization. Its "data structure" is the policy itself, which can be represented in code or a database.
  • Efficient ABAC evaluation is a critical design challenge. The naive approach of a linear scan of policies doesn't scale. The solution is to pre-compile policies into an optimized structure like a policy tree (PolTree) to enable fast lookups at runtime.
  • ReBAC is an evolution of these concepts, modeling authorization as a graph of relationships, ideal for large-scale sharing systems.

Preview of the Next Lesson:

We have now defined the policy models. The next logical step is to design the system that executes them. In our next lesson, we will focus on the learning outcome: "Architect a low-latency policy evaluation engine for authorization, including caching and distribution strategies." We'll design the runtime components (like the Policy Decision Point and Policy Information Point) that bring our RBAC and ABAC models to life in a scalable, distributed environment.

Can't find a good explanation? Sign up and we'll make it for you

Sign up