<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ access control - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ access control - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 27 Jul 2026 22:41:46 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/access-control/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How Attribute-Based Access Control Helps You Write Better Authorization Rules ]]>
                </title>
                <description>
                    <![CDATA[ Every application that handles user data eventually hits the same problem: not all users should see the same things. A junior nurse should not be able to access every patient record in the hospital. A ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-attribute-based-access-control-helps-you-write-better-authorization-rules/</link>
                <guid isPermaLink="false">6a21b44e09761aac249579f9</guid>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ access control ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aiyedogbon Abraham ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jun 2026 17:22:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/1bcd9989-cf38-4375-a0ed-03cf1bd3c3b8.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every application that handles user data eventually hits the same problem: not all users should see the same things.</p>
<p>A junior nurse should not be able to access every patient record in the hospital. A contractor should not be able to read internal financial reports. An employee logged in from an unrecognized device at 2AM probably should not be editing production configuration files.</p>
<p>Simple role-based systems handle obvious cases well. But as applications grow and access rules become more nuanced, those systems start to crack. You end up creating more and more specific roles, like <code>finance_viewer</code>, <code>finance_viewer_us_only</code>, <code>finance_viewer_us_only_readonly</code>, until the roles themselves become unmanageable.</p>
<p>Attribute-Based Access Control (ABAC) was designed to solve exactly this problem. It shifts from "what role does this user have?" to "what do we know about this user, this resource, and this situation?" and makes access decisions based on all of those factors together.</p>
<p>In this guide, you'll learn how ABAC works, how it evolved from earlier access control models, how policies are structured, how to implement it in code, and when to use it.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-access-control-has-evolved">How Access Control Has Evolved</a></p>
</li>
<li><p><a href="#heading-what-is-attribute-based-access-control">What is Attribute-Based Access Control?</a></p>
</li>
<li><p><a href="#heading-the-four-building-blocks-of-abac">The Four Building Blocks of ABAC</a></p>
</li>
<li><p><a href="#heading-how-an-abac-decision-is-made">How an ABAC Decision is Made</a></p>
</li>
<li><p><a href="#heading-how-to-write-abac-policies">How to Write ABAC Policies</a></p>
</li>
<li><p><a href="#heading-how-to-implement-abac-in-code">How to Implement ABAC in Code</a></p>
</li>
<li><p><a href="#heading-abac-vs-rbac-when-to-use-which">ABAC vs RBAC: When to Use Which</a></p>
</li>
<li><p><a href="#heading-real-world-use-cases">Real-World Use Cases</a></p>
</li>
<li><p><a href="#heading-enterprise-abac-considerations">Enterprise ABAC Considerations</a></p>
</li>
<li><p><a href="#heading-limitations-and-challenges">Limitations and Challenges</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-glossary">Glossary</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most from this article, you should have:</p>
<ul>
<li><p>A basic understanding of web authentication (logins, sessions, tokens)</p>
</li>
<li><p>Familiarity with how users and resources relate in applications</p>
</li>
<li><p>Some experience reading JavaScript or pseudocode</p>
</li>
</ul>
<p>No prior knowledge of access control theory is required.</p>
<h2 id="heading-how-access-control-has-evolved">How Access Control Has Evolved</h2>
<p>To understand why ABAC exists, it helps to understand what came before it and why each generation fell short.</p>
<h3 id="heading-discretionary-and-mandatory-access-control">Discretionary and Mandatory Access Control</h3>
<p>Early access control models emerged from Department of Defense applications in the 1960s and 1970s. According to NIST Special Publication 800-162, these were Discretionary Access Control (DAC) and Mandatory Access Control (MAC).</p>
<p>In DAC, the owner of a resource decides who can access it. Think of a file on your computer where you choose who can read or edit it. In MAC, access is governed by a central authority using labels like "Classified" or "Top Secret." The system enforces these labels, not individual owners.</p>
<p>Both worked for their original purposes but didn't scale well to the complexity of modern networked systems.</p>
<h3 id="heading-identity-based-access-control-and-access-control-lists">Identity-Based Access Control and Access Control Lists</h3>
<p>As networks grew, identity-based access control (IBAC) became common. The most familiar implementation is the Access Control List (ACL), a list of users or groups attached to a resource, specifying what each can do.</p>
<p>ACLs are simple and transparent, but they create a management burden as systems grow. Every new user needs to be added to every relevant list. Every permission change means hunting through lists across multiple resources. And when someone leaves the organization, you need to find and remove them everywhere.</p>
<p>Failure to do this consistently leads to users accumulating privileges they should no longer have.</p>
<h3 id="heading-role-based-access-control">Role-Based Access Control</h3>
<p>Role-Based Access Control (RBAC) was a major step forward. Instead of assigning permissions directly to users, RBAC assigns them to roles. Users are then assigned roles. A hospital might have roles like <code>nurse</code>, <code>doctor</code>, <code>admin</code>, and <code>billing_staff</code>, each with different permissions.</p>
<p>This made administration much more manageable. Adding a new employee means assigning them appropriate roles. Removing an employee means removing their roles. Changing what nurses can do means updating the nurse role once.</p>
<p>RBAC became widely adopted and is still the right choice for many applications. But it has a structural weakness: as permission requirements become more granular, you have to create more specific roles. A nurse who can only see patients on their floor, only during their shift, or only for certain record types, needs a very specific role, or a combination of roles that interacts in complicated ways.</p>
<p>This proliferation is called "role explosion." The roles multiply until they are as difficult to manage as the individual permissions RBAC was supposed to replace.</p>
<h3 id="heading-attribute-based-access-control">Attribute-Based Access Control</h3>
<p>ABAC emerged as a response to role explosion. Instead of assigning roles that bundle fixed permissions, ABAC evaluates the actual characteristics of the user, the resource, and the context at the moment of every access request.</p>
<p>A nurse gets access to a patient record not because they have the <code>nurse</code> role, but because their job title is "Nurse Practitioner," the patient is on their assigned floor, it's currently their shift, and the record type is within their scope of care. Change any of those facts, and the access decision changes accordingly.</p>
<p>As NIST SP 800-162 defines it, ABAC is:</p>
<blockquote>
<p>"an access control method where subject requests to perform operations on objects are granted or denied based on assigned attributes of the subject, assigned attributes of the object, environment conditions, and a set of policies that are specified in terms of those attributes and conditions."</p>
</blockquote>
<h2 id="heading-what-is-attribute-based-access-control">What is Attribute-Based Access Control?</h2>
<p>ABAC is a logical access control model where every access decision is made by evaluating a set of rules against the current values of attributes. Nothing is pre-computed or cached in role assignments. Every time a user tries to do something, the system asks: given what we know about this user, this resource, and this moment, should this be allowed?</p>
<p>This makes ABAC highly precise and highly dynamic. Permissions don't accumulate over time. They don't need manual cleanup when someone's role changes. The system simply evaluates the current state of attributes every time.</p>
<p>The model is formally described in NIST's guide to ABAC as being capable of enforcing both Discretionary Access Control and Mandatory Access Control concepts, making it more expressive than models that only support one or the other.</p>
<p>Companies like Axiomatics, major government agencies, and large enterprises managing cross-organizational data sharing all rely on ABAC for its ability to scale security policies across complex environments.</p>
<h2 id="heading-the-four-building-blocks-of-abac">The Four Building Blocks of ABAC</h2>
<p>Every ABAC system is built from four types of information. Understanding these clearly is the key to understanding how ABAC works.</p>
<h3 id="heading-1-subject-attributes">1. Subject Attributes</h3>
<p>The subject is whoever or whatever is requesting access. This is usually a user, but it can also be a service, an application, or an automated system, what NIST calls a Non-Person Entity (NPE).</p>
<p>Subject attributes describe who the subject is:</p>
<pre><code class="language-plaintext">user.jobTitle         = "Nurse Practitioner"
user.department       = "Cardiology"
user.clearanceLevel   = "Confidential"
user.employmentStatus = "Active"
user.location         = "Floor 3"
user.shiftActive      = true
</code></pre>
<p>These attributes are typically sourced from an identity provider, HR system, or user directory. They're facts about the user that can be used in policies.</p>
<h3 id="heading-2-object-attributes-resource-attributes">2. Object Attributes (Resource Attributes)</h3>
<p>The object is whatever the subject is trying to access. This could be a file, a database record, an API endpoint, a service, or any other protected resource.</p>
<p>Object attributes describe what the resource is:</p>
<pre><code class="language-plaintext">record.type           = "PatientMedical"
record.floor          = "Floor 3"
record.sensitivity    = "High"
record.owner          = "Dr. Williams"
record.department     = "Cardiology"
</code></pre>
<p>Object attributes are typically assigned when a resource is created and updated throughout its lifecycle. They're facts about the resource that determine who should be able to access it.</p>
<h3 id="heading-3-action-attributes">3. Action Attributes</h3>
<p>The action is what the subject is trying to do to the object. Common actions include read, write, edit, delete, copy, execute, and share.</p>
<p>In many ABAC implementations, the action itself has attributes:</p>
<pre><code class="language-plaintext">action.type           = "read"
action.bulk           = false
</code></pre>
<p>Policies can restrict which actions are allowed independently of the other attributes. A user might be able to read a document but not delete it, even if all their other attributes match.</p>
<h3 id="heading-4-environment-conditions">4. Environment Conditions</h3>
<p>Environment conditions are contextual factors that don't belong to either the subject or the object, but that should influence the access decision. NIST describes these as "dynamic factors, independent of subject and object, that may be used as attributes at decision time to influence an access decision."</p>
<p>Examples include:</p>
<pre><code class="language-plaintext">environment.time           = "14:30"
environment.dayOfWeek      = "Wednesday"
environment.userLocation   = "Corporate Office"
environment.ipAddress      = "192.168.1.10"
environment.deviceStatus   = "compliant"
environment.threatLevel    = "low"
</code></pre>
<p>Environment conditions are what make ABAC truly dynamic. The same user, the same resource, and the same action might be allowed during business hours on a trusted device but denied at midnight from an unknown IP address.</p>
<h2 id="heading-how-an-abac-decision-is-made">How an ABAC Decision is Made</h2>
<p>When a subject tries to perform an action on an object, the ABAC system runs through a specific process:</p>
<h3 id="heading-step-1-collect-attributes">Step 1: Collect Attributes</h3>
<p>The system gathers current attributes for the subject, object, action, and environment. This might involve querying a user directory, reading resource metadata, and checking current time and location.</p>
<h3 id="heading-step-2-find-applicable-policies">Step 2: Find Applicable Policies</h3>
<p>The system identifies which policies apply to this particular request. A request to read a patient record might have several policies that apply: one about clinical staff access, one about after-hours access, and one about record sensitivity levels.</p>
<h3 id="heading-step-3-evaluate-each-policy">Step 3: Evaluate Each Policy</h3>
<p>Each applicable policy evaluates the collected attributes and returns permit or deny.</p>
<h3 id="heading-step-4-reconcile-conflicts">Step 4: Reconcile Conflicts</h3>
<p>If multiple policies apply and they conflict, the system uses predefined combining rules. Common approaches are "deny overrides" (if any policy says deny, the request is denied) or "permit overrides" (if any policy says permit, the request is permitted).</p>
<h3 id="heading-step-5-enforce-the-decision">Step 5: Enforce the Decision</h3>
<p>The system grants or denies access based on the final decision.</p>
<p>This process happens every time an access request is made. There's no caching of role assignments or pre-computed permission tables. The decision reflects the current state of all attributes at the moment of the request.</p>
<h2 id="heading-how-to-write-abac-policies">How to Write ABAC Policies</h2>
<p>Policies are the logic at the heart of ABAC. They're written as conditional rules that reference attributes. A well-written policy reads like a business rule, because that's exactly what it is.</p>
<h3 id="heading-simple-boolean-policy">Simple Boolean Policy</h3>
<p>The most basic form evaluates whether certain attributes match:</p>
<pre><code class="language-javascript">// Policy: Only active employees can access internal resources
function canAccessInternalResource(user) {
  return user.employmentStatus === "Active";
}
</code></pre>
<p><strong>What this does:</strong> Checks a single attribute, employment status, before allowing access. Any inactive, suspended, or terminated user is denied, regardless of their roles or past access history.</p>
<h3 id="heading-multi-attribute-policy">Multi-Attribute Policy</h3>
<p>Real policies typically combine multiple attributes:</p>
<pre><code class="language-javascript">// Policy: A nurse can read a patient record
// if the patient is on their assigned floor
// and during their active shift

function canReadPatientRecord(user, record, environment) {
  const isNurse = user.jobTitle === "Nurse Practitioner";
  const isAssignedFloor = user.assignedFloor === record.floor;
  const isActiveDuty = user.shiftActive === true;

  return isNurse &amp;&amp; isAssignedFloor &amp;&amp; isActiveDuty;
}
</code></pre>
<p><strong>What this does:</strong> Combines three conditions using AND logic. All three must be true for access to be granted. Change the nurse's floor assignment, and they immediately lose access to records on the previous floor, without any manual intervention.</p>
<h3 id="heading-environment-aware-policy">Environment-Aware Policy</h3>
<p>Adding environment conditions makes policies context-sensitive:</p>
<pre><code class="language-javascript">// Policy: Users can only access sensitive financial records
// during business hours from the corporate network

function canAccessSensitiveFinancialRecord(user, record, environment) {
  const isFinanceStaff = user.department === "Finance";
  const isHighSensitivity = record.sensitivity === "High";
  
  // If this is a high-sensitivity record, apply time and location controls
  if (isHighSensitivity) {
    const currentHour = new Date(environment.timestamp).getHours();
    const isBusinessHours = currentHour &gt;= 9 &amp;&amp; currentHour &lt; 17;
    const isCorporateNetwork = environment.ipRange === "corporate";

    return isFinanceStaff &amp;&amp; isBusinessHours &amp;&amp; isCorporateNetwork;
  }

  // Lower sensitivity records only require finance department membership
  return isFinanceStaff;
}
</code></pre>
<p><strong>What this does:</strong> Applies stricter controls to higher-sensitivity resources. The same user gets access to low-sensitivity records at any time, but high-sensitivity records require them to be on the corporate network during business hours. The policy logic mirrors the actual business rule: sensitive data needs more protection.</p>
<h3 id="heading-ownership-based-policy">Ownership-Based Policy</h3>
<p>ABAC can also implement discretionary ownership rules:</p>
<pre><code class="language-javascript">// Policy: A user can edit a document
// if they own it, or if they have editor permissions
// and the document isn't locked

function canEditDocument(user, document, action) {
  const isOwner = document.ownerId === user.id;
  const hasEditorPermission = user.permissions.includes("document.edit");
  const isUnlocked = document.status !== "locked";

  return (isOwner || hasEditorPermission) &amp;&amp; isUnlocked;
}
</code></pre>
<p><strong>What this does:</strong> Combines ownership (an attribute of the relationship between user and document) with explicit permissions and resource state. An editor can't edit a locked document even if they have the edit permission. An owner can edit their own documents but not locked ones.</p>
<h2 id="heading-how-to-implement-abac-in-code">How to Implement ABAC in Code</h2>
<p>Let's build a simple ABAC evaluation engine that puts these pieces together.</p>
<h3 id="heading-step-1-define-the-attribute-structure">Step 1: Define the Attribute Structure</h3>
<p>First, define clear data structures for your attributes:</p>
<pre><code class="language-javascript">// A user (subject) requesting access
const user = {
  id: "user-123",
  name: "Sarah Chen",
  department: "Cardiology",
  jobTitle: "Nurse Practitioner",
  clearanceLevel: 2,
  assignedFloor: "Floor 3",
  shiftActive: true,
  employmentStatus: "Active"
};

// A resource (object) being accessed
const patientRecord = {
  id: "record-456",
  type: "PatientMedical",
  floor: "Floor 3",
  sensitivity: 2,
  ownerId: "doctor-789",
  department: "Cardiology"
};

// Environment conditions
const environment = {
  timestamp: new Date().toISOString(),
  ipAddress: "10.0.1.25",
  ipRange: "corporate",
  deviceCompliant: true
};
</code></pre>
<h3 id="heading-step-2-write-policy-functions">Step 2: Write Policy Functions</h3>
<p>Write individual policies as pure functions that take attributes and return boolean values:</p>
<pre><code class="language-javascript">// policies/patientRecord.js

// Policy 1: User must be active and clinical staff
function isClinicalStaff(user) {
  const clinicalTitles = [
    "Nurse Practitioner",
    "Physician",
    "Resident",
    "Medical Assistant"
  ];
  return (
    user.employmentStatus === "Active" &amp;&amp;
    clinicalTitles.includes(user.jobTitle)
  );
}

// Policy 2: Record must be within the user's assigned area
function isAssignedToRecord(user, record) {
  return (
    user.department === record.department &amp;&amp;
    user.assignedFloor === record.floor
  );
}

// Policy 3: User must be on active shift
function isOnActiveShift(user) {
  return user.shiftActive === true;
}

// Policy 4: High-sensitivity records require compliant devices
function meetsDeviceRequirements(record, environment) {
  if (record.sensitivity &gt;= 3) {
    return environment.deviceCompliant === true;
  }
  return true; // No device requirement for lower sensitivity
}
</code></pre>
<p><strong>What this does:</strong> Each policy is a small, focused function. This makes policies easy to test individually, easy to read, and easy to reuse across different access decisions. A policy for "is this user clinical staff" can be applied to many different resource types.</p>
<h3 id="heading-step-3-build-an-evaluation-engine">Step 3: Build an Evaluation Engine</h3>
<p>Combine your policies into a decision engine:</p>
<pre><code class="language-javascript">// abac/engine.js

function evaluateAccess(user, resource, action, environment, policies) {
  // Collect all policy results
  const results = policies.map(policy =&gt; {
    try {
      return policy(user, resource, action, environment);
    } catch (error) {
      console.error(`Policy evaluation error: ${error.message}`);
      return false; // Fail closed: deny on error
    }
  });

  // Deny-overrides: if any policy denies, access is denied
  return results.every(result =&gt; result === true);
}

// Assemble policies for reading patient records
const readPatientRecordPolicies = [
  (user) =&gt; isClinicalStaff(user),
  (user, record) =&gt; isAssignedToRecord(user, record),
  (user) =&gt; isOnActiveShift(user),
  (user, record, action, environment) =&gt; meetsDeviceRequirements(record, environment)
];

// Make an access decision
const canRead = evaluateAccess(
  user,
  patientRecord,
  "read",
  environment,
  readPatientRecordPolicies
);

console.log(`Access ${canRead ? "granted" : "denied"}`);
// → Access granted (all conditions met)
</code></pre>
<p><strong>What this does:</strong> The engine loops through each policy function, passing in the relevant attributes. If all policies return true, access is granted. If any returns false, access is denied. This is called "deny-overrides combining". The <code>try-catch</code> ensures that if a policy throws an error, access is denied rather than granted, following the security principle of fail-closed.</p>
<h3 id="heading-step-4-add-attribute-collection">Step 4: Add Attribute Collection</h3>
<p>In a real application, attributes come from multiple sources:</p>
<pre><code class="language-javascript">// attributes/collector.js

async function collectAttributes(userId, resourceId) {
  // Collect in parallel for performance
  const [user, resource, environment] = await Promise.all([
    fetchUserAttributes(userId),      // From identity provider or HR system
    fetchResourceAttributes(resourceId), // From resource metadata store
    collectEnvironmentConditions()    // Time, IP, device status
  ]);

  return { user, resource, environment };
}

async function fetchUserAttributes(userId) {
  // This would query your user directory, LDAP, or identity provider
  const user = await userDirectory.findById(userId);
  const shift = await shiftService.getActiveShift(userId);
  
  return {
    ...user,
    shiftActive: shift !== null,
    assignedFloor: shift?.floor || null
  };
}

async function collectEnvironmentConditions() {
  return {
    timestamp: new Date().toISOString(),
    ipAddress: request.ip,
    ipRange: await networkService.classifyIP(request.ip),
    deviceCompliant: await deviceService.checkCompliance(request.deviceId)
  };
}
</code></pre>
<p><strong>What this does:</strong> Attribute collection is separated from policy evaluation. This is an important design decision: it means you can test policies with any attribute values without needing real users or resources. It also means you can swap out the source of attributes (say, moving from an on-premise directory to a cloud identity provider) without changing your policies.</p>
<h3 id="heading-step-5-integrate-with-your-api">Step 5: Integrate with Your API</h3>
<p>Use the evaluation engine in your API handlers:</p>
<pre><code class="language-javascript">// middleware/abac.js

function requireAccess(action, resourceType) {
  return async (req, res, next) =&gt; {
    try {
      const { user, resource, environment } = await collectAttributes(
        req.user.id,
        req.params.id
      );

      const policies = getPoliciesFor(resourceType, action);
      const allowed = evaluateAccess(user, resource, action, environment, policies);

      if (!allowed) {
        // Log the denial for audit purposes
        auditLog.record({
          userId: req.user.id,
          resourceId: req.params.id,
          action,
          decision: "denied",
          timestamp: new Date()
        });

        return res.status(403).json({ error: "Access denied" });
      }

      next();
    } catch (error) {
      // Fail closed: deny access on unexpected errors
      return res.status(403).json({ error: "Access denied" });
    }
  };
}

// Use in route definitions
app.get(
  "/patient-records/:id",
  authenticate(),                               // First verify identity
  requireAccess("read", "patientRecord"),       // Then evaluate ABAC
  patientRecordController.getById               // Then handle the request
);
</code></pre>
<p><strong>What this does:</strong> The ABAC check lives in middleware that runs between authentication and the route handler. Authentication establishes who the user is. ABAC decides whether that user can do what they're trying to do. This separation keeps authorization logic out of your business logic.</p>
<h2 id="heading-abac-vs-rbac-when-to-use-which">ABAC vs RBAC: When to Use Which</h2>
<p>RBAC isn't obsolete. It's genuinely the right choice for many applications. The question is which model fits your specific access requirements.</p>
<h3 id="heading-rbac-strengths">RBAC Strengths</h3>
<p>RBAC is simple to understand, simple to implement, and simple to audit. If you can describe your access requirements as a list of roles with fixed permissions, RBAC works well. Most SaaS applications start with RBAC and it serves them fine for years.</p>
<p>A typical RBAC check looks like:</p>
<pre><code class="language-javascript">// Simple RBAC: does the user have the required role?
function canAccess(user, requiredRole) {
  return user.roles.includes(requiredRole);
}
</code></pre>
<p>It's fast, clear, and easy to debug. When something goes wrong, you check which roles the user has and which roles the resource requires.</p>
<h3 id="heading-where-rbac-breaks-down">Where RBAC Breaks Down</h3>
<p>RBAC struggles when permissions need to depend on factors that aren't captured by a role. If you need to express "finance managers can view financial records, but only for their own region, and only during business hours," you're outside what a role alone can express cleanly.</p>
<p>You either need an extremely specific role (<code>finance_manager_us_east_business_hours</code>) that creates the role explosion problem, or you add conditional logic to your application code that effectively recreates ABAC, just in a less organized way.</p>
<h3 id="heading-rbac-vs-abac-comparison">RBAC vs ABAC Comparison</h3>
<table>
<thead>
<tr>
<th>Factor</th>
<th>RBAC</th>
<th>ABAC</th>
</tr>
</thead>
<tbody><tr>
<td>Logic</td>
<td>Permissions assigned to roles, roles assigned to users</td>
<td>Policies evaluate attributes at decision time</td>
</tr>
<tr>
<td>Granularity</td>
<td>Coarse-grained</td>
<td>Fine-grained and context-aware</td>
</tr>
<tr>
<td>Flexibility</td>
<td>Low, new rules require new roles</td>
<td>High, update policies without changing roles</td>
</tr>
<tr>
<td>Scalability</td>
<td>Role explosion under complexity</td>
<td>Scales with policy complexity, not role count</td>
</tr>
<tr>
<td>Auditability</td>
<td>Simple, check role assignments</td>
<td>Requires logging attributes at decision time</td>
</tr>
<tr>
<td>Complexity</td>
<td>Low</td>
<td>Higher, more moving parts</td>
</tr>
<tr>
<td>Best for</td>
<td>Simple, stable permission structures</td>
<td>Complex, dynamic, or context-dependent permissions</td>
</tr>
</tbody></table>
<h3 id="heading-combining-both-models">Combining Both Models</h3>
<p>RBAC and ABAC work well together. A common pattern is to use RBAC for coarse-grained access control (which sections of your application can this user see?) and ABAC for fine-grained control within those sections (which specific records can they access?).</p>
<p>For example, a role might grant access to the patient records section of a hospital system. Within that section, ABAC policies determine which specific records a user can view or edit based on their department, assigned floor, and active shift.</p>
<h2 id="heading-real-world-use-cases">Real-World Use Cases</h2>
<h3 id="heading-healthcare-records-management">Healthcare Records Management</h3>
<p>Healthcare is one of the clearest examples of why ABAC matters. Patient privacy regulations require precise access control, and patient care requires that the right staff can access records quickly when they need them.</p>
<p>An ABAC policy in a hospital might allow a nurse to view a patient's record only when:</p>
<ol>
<li><p>the patient is currently admitted to the nurse's assigned floor,</p>
</li>
<li><p>the nurse is on an active shift,</p>
</li>
<li><p>the access occurs from within the hospital network,</p>
</li>
<li><p>and the record type is within the nurse's care scope.</p>
</li>
</ol>
<p>According to WorkOS's ABAC analysis, in emergency situations ABAC systems can automatically expand access rights. For example, an ER doctor automatically gains broader access to patient records to provide immediate care, with this access being time-bound and closely monitored.</p>
<p>All of these rules would require dozens of roles in an RBAC system, and those roles would still struggle to handle the emergency access scenario dynamically.</p>
<h3 id="heading-corporate-data-access">Corporate Data Access</h3>
<p>Large enterprises typically have employees across departments, roles, locations, and clearance levels who need different views of the same underlying data. A document might be accessible to finance managers in the US region during business hours, accessible to executives globally at any time, but inaccessible to contractors entirely.</p>
<p>ABAC expresses all of these rules in policies. As employees change departments, go on leave, or change roles, their attributes update in the identity system and their access changes automatically, with no manual ACL updates required.</p>
<h3 id="heading-government-and-classified-information">Government and Classified Information</h3>
<p>The US federal government's adoption of ABAC is described in NIST SP 800-162, which was developed to address the Federal Identity, Credential, and Access Management (FICAM) requirements. Federal agencies deal with information shared across organizational boundaries, with varying classification levels and need-to-know requirements.</p>
<p>ABAC allows an analyst in one agency to access information from another agency without requiring the second agency to pre-provision an account for them. The analyst's clearance attributes, organizational affiliation, and project assignments are evaluated against the resource's classification and access rules at the time of the request.</p>
<h3 id="heading-multi-tenant-saas-applications">Multi-Tenant SaaS Applications</h3>
<p>SaaS applications that serve multiple organizations need to ensure strict data isolation between tenants while supporting complex permission structures within each tenant.</p>
<p>ABAC handles this naturally. A resource attribute like <code>record.tenantId</code> is evaluated against the user attribute <code>user.tenantId</code>, and no cross-tenant access is possible through policy. Within a tenant, ABAC supports as much complexity as the tenant's policies require.</p>
<h2 id="heading-enterprise-abac-considerations">Enterprise ABAC Considerations</h2>
<p>Deploying ABAC at enterprise scale introduces several challenges that don't exist in smaller implementations.</p>
<h3 id="heading-policy-administration">Policy Administration</h3>
<p>Policies need to be authored, reviewed, tested, and deployed. According to NIST SP 800-162, this requires a Policy Administration Point (PAP), an interface for creating and managing policies. Without proper tooling, policies become difficult to audit and maintain.</p>
<p>In practice, this means treating policies like code: version control, code review, and automated testing.</p>
<h3 id="heading-attribute-quality-and-freshness">Attribute Quality and Freshness</h3>
<p>ABAC is only as good as the attributes it evaluates. If user attributes are stale, for example, for a user who changed departments but whose directory entry hasn't been updated, the access decisions will be wrong.</p>
<p>NIST warns that "attributes that are not refreshed as often will ultimately be less secure than attributes that are refreshed in real time." Building reliable attribute pipelines from authoritative sources is often the hardest part of ABAC deployment.</p>
<h3 id="heading-performance">Performance</h3>
<p>Evaluating policies on every request has a performance cost. Each evaluation may require fetching attributes from multiple sources. To manage this, many implementations use attribute caching, but caching introduces the staleness problem described above.</p>
<p>The solution is to cache with appropriate TTLs (time-to-live values) based on how quickly each attribute type can change. A user's department changes rarely and can be cached for hours. A user's active shift status might change every 8 hours and needs a shorter cache. Real-time location might not be cacheable at all.</p>
<h3 id="heading-audit-logging">Audit Logging</h3>
<p>Because ABAC makes decisions dynamically, auditing requires logging the attributes used in each decision, not just the decision itself. A log entry that says "access denied" is only useful if it also captures why access was denied and which attributes failed to satisfy which policies.</p>
<p>NIST notes that without tracking attribute values at decision time, accountability requirements can't be met.</p>
<h2 id="heading-limitations-and-challenges">Limitations and Challenges</h2>
<p>ABAC is powerful, but it's not the right solution for every access control problem. It's worth being honest about its limitations before committing to an implementation.</p>
<p><strong>Complexity</strong>: According to NIST SP 800-162, "an ABAC system is more complicated, and therefore more costly to implement and maintain, than simpler access control systems." The flexibility that makes ABAC powerful also makes it harder to reason about. A user asking "why can't I access this?" requires examining all the attributes that were evaluated and which conditions weren't met.</p>
<p><strong>Policy Conflicts</strong>: In complex systems with many policies, conflicts between policies can occur. Two policies might individually seem correct but together produce unexpected results. Resolving these conflicts requires clear precedence rules and careful policy design.</p>
<p><strong>Attribute Management Overhead</strong>: Maintaining accurate attributes across large user populations requires investment in identity infrastructure. Attributes from different systems need to be normalized, validated, and kept synchronized. As NIST describes it, organizations need an entire attribute management infrastructure, not just a policy engine.</p>
<p><strong>Testing is Hard</strong>: Because access depends on the combination of potentially dozens of attributes, testing edge cases comprehensively requires thought. A policy that works correctly for typical cases might behave unexpectedly for unusual attribute combinations.</p>
<p><strong>Not Always Worth the Investment</strong>: For applications with straightforward access requirements, ABAC introduces unnecessary complexity. If your needs can be expressed cleanly as a set of roles with fixed permissions, RBAC is the better choice.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Attribute-Based Access Control represents a genuine evolution in how applications manage authorization. Rather than maintaining ever-growing lists of roles and permissions, ABAC evaluates the actual characteristics of users, resources, and context at the moment of every request.</p>
<p>It solves the role explosion problem that plagues complex RBAC implementations. It enables access rules that reflect real business policies rather than technical approximations of them. It handles dynamic scenarios, emergencies, time-based restrictions, and cross-organizational access that are difficult or impossible to express with static roles.</p>
<p>But ABAC isn't universally better. It's more complex to build, harder to debug, and requires investment in attribute management infrastructure that simpler models don't need. Many applications are well-served by RBAC, and some use RBAC and ABAC together.</p>
<p>The right question isn't "should I use ABAC?" It's "are my access requirements complex enough that the investment in ABAC pays off?" If your access rules change frequently, depend on resource or environment context, or need to scale across organizational boundaries, ABAC is worth serious consideration.</p>
<p>Start by identifying where your current access control model is breaking down. If you're creating roles to represent every edge case, if you're writing conditional logic inside route handlers that checks specific attribute values, or if users are accumulating permissions they should no longer have, those are signals that a more expressive model would help.</p>
<p>ABAC is the tool for when roles aren't enough.</p>
<h2 id="heading-glossary">Glossary</h2>
<p><strong>ABAC (Attribute-Based Access Control)</strong>: An access control method where authorization decisions are made by evaluating policies against the attributes of subjects, objects, actions, and environment conditions. Defined by NIST as the approach where "subject requests to perform operations on objects are granted or denied based on assigned attributes."</p>
<p><strong>Subject</strong>: The entity requesting access to a resource. Usually a human user, but can also be a service, automated process, or device. Also called the "requestor."</p>
<p><strong>Object</strong>: The resource being protected, such as a file, database record, API endpoint, service, or any system resource whose access is managed by the ABAC system.</p>
<p><strong>Attribute</strong>: A characteristic of a subject, object, action, or environment expressed as a name-value pair. For example, <code>user.department = "Finance"</code> or <code>record.sensitivity = "High"</code>.</p>
<p><strong>Subject Attributes</strong>: Properties describing the user or service making the request, such as job title, department, clearance level, or current location.</p>
<p><strong>Object Attributes</strong>: Properties describing the resource being accessed, such as its type, owner, sensitivity level, or department.</p>
<p><strong>Environment Conditions</strong>: Contextual factors independent of both subject and object that influence access decisions. Examples include time of day, day of week, IP address, device compliance status, or current threat level.</p>
<p><strong>Policy</strong>: A rule or set of rules that evaluates attribute values to determine whether a specific access request should be permitted or denied. ABAC policies are typically written as logical conditions.</p>
<p><strong>Policy Decision Point (PDP)</strong>: The component of an ABAC system that evaluates policies and attributes to compute an access decision.</p>
<p><strong>Policy Enforcement Point (PEP)</strong>: The component that intercepts access requests and enforces the decisions made by the PDP.</p>
<p><strong>Policy Information Point (PIP)</strong>: The component that retrieves attribute values needed by the PDP to make decisions.</p>
<p><strong>Policy Administration Point (PAP)</strong>: The component that provides an interface for creating, testing, and managing policies.</p>
<p><strong>RBAC (Role-Based Access Control)</strong>: An access control model that assigns permissions to roles and users to roles. Simpler than ABAC but less expressive for complex, dynamic access requirements.</p>
<p><strong>Role Explosion</strong>: The proliferation of increasingly specific roles in an RBAC system as access requirements become more granular, eventually making the roles as difficult to manage as individual permissions.</p>
<p><strong>DAC (Discretionary Access Control)</strong>: An access control model where resource owners control who can access their resources. Common in file systems.</p>
<p><strong>MAC (Mandatory Access Control)</strong>: An access control model where access is governed by a central authority using classification labels, independent of resource owner preferences.</p>
<p><strong>ACL (Access Control List)</strong>: A list associated with a resource that specifies which users or groups have which permissions. Common in identity-based access control systems.</p>
<p><strong>Non-Person Entity (NPE)</strong>: A subject that is not a human user, such as an automated service, application, or network device, that can request access to resources.</p>
<p><strong>Attribute Caching</strong>: Storing previously retrieved attribute values to improve performance, at the cost of potentially using stale data for access decisions.</p>
<p><strong>Deny-Overrides Combining</strong>: A policy combining rule where if any applicable policy returns deny, the overall decision is deny, regardless of other policies that may return permit.</p>
<p><strong>Fail-Closed</strong>: A security design principle where unexpected errors or missing information result in access being denied rather than granted, reducing the risk of unauthorized access.</p>
<p><em>Source: Definitions adapted from NIST Special Publication 800-162, Guide to Attribute Based Access Control (ABAC) Definition and Considerations, January 2014 (with updates through August 2019).</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Scalable Access Control for Your Web App [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ Access control is crucial for preventing unauthorized access and ensuring that only the right people can access sensitive data in your application. As your app grows in complexity, so does the challenge of enforcing permissions in a clean and efficie... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-scalable-access-control-for-your-web-app/</link>
                <guid isPermaLink="false">67a269edea59b2f402186a7d</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ permissions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ access control ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ABAC ]]>
                    </category>
                
                    <category>
                        <![CDATA[ casl ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Samhitha Rama Prasad ]]>
                </dc:creator>
                <pubDate>Tue, 04 Feb 2025 19:26:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738695897990/7a5962ce-9c4a-4e7c-bdeb-520dccc5d240.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Access control is crucial for preventing unauthorized access and ensuring that only the right people can access sensitive data in your application. As your app grows in complexity, so does the challenge of enforcing permissions in a clean and efficient way.</p>
<p>In this handbook, we’ll explore various access control mechanisms and walk through two approaches for building a scalable Attribute-Based Access Control solution in React.</p>
<p>First, we'll examine CASL, a popular open-source authorization library. Then, we’ll build a custom solution from scratch to deepen your understanding of how to design a flexible permissions validation system.</p>
<p>This guide includes detailed code walkthroughs for both approaches, covering key concepts such as state management, custom hooks, and caching/conditional queries using Redux Toolkit.</p>
<p>If you plan to implement the code, you should have a basic understanding of how a web app using state management works. But even if you're not coding along, you’ll still gain valuable insights into the design patterns and best practices behind creating a robust permissions validation system.</p>
<p>Let’s dive in!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-access-control-how-is-it-different-from-authz-authn-and-permissions">What is access control? How is it different from AuthZ, AuthN and permissions?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-multi-layered-access-control">Multi-layered Access Control</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-hogwarts-in-harmony-a-unified-defense">Hogwarts in Harmony: A Unified Defense</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-access-control-models">Access Control Models</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-abac">Why ABAC?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-attribute-based-access-control-in-depth">Attribute-Based Access Control In Depth</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-core-components">Core Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-abac-work">How does ABAC work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-who-defines-abac-policies">Who defines ABAC policies?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-where-should-you-enforce-it-back-end-or-front-end">Where should you enforce it — back-end or front-end?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-where-are-policies-defined">Where are policies defined?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-1-implementing-permissions-with-casl">1: Implementing Permissions with CASL</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-build-your-custom-permissions-validation-framework">2: Build Your Custom Permissions Validation Framework</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-policy-definition-using-policy-as-code">Policy Definition using Policy as Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-workflow-overview">Workflow Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-policy-validation">Policy Validation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-policy-enforcement">Policy Enforcement</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-summarize">Let’s Summarize</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-further-scaling-considerations">Further Scaling Considerations</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-access-control-how-is-it-different-from-authz-authn-and-permissions">What is Access Control? How is it Different from AuthZ, AuthN, and Permissions?</h2>
<p>Let me break down these terms using the example of an airport.</p>
<p>When you arrive at the check-in counter, you present your passport to verify your identity. <strong>Authentication</strong> (Who are you?) is the process of confirming that you are who you say you are.</p>
<p>Once your identity is confirmed, the airline checks if you are authorized to board the flight by verifying your ticket, or if you are authorized to access the lounge by reviewing your membership status, class of travel, or loyalty program tier. <strong>Authorization</strong> (What are you allowed to do?) is about determining what specific resources you are permitted to access.</p>
<p><strong>Permissions</strong> (What specific actions can you take?) are the granular details of what you're allowed to do within the scope of your authorization. If you’re authorized to board the flight and access the lounge, your permissions might include: sitting at the boarding gate, relaxing in the lounge, shopping in duty-free, or if you’re staff, accessing restricted areas.</p>
<p><strong>Access control</strong> refers to the measures in place to enforce authorization policies. These are the rules the airport follows to validate boarding passes or lounge access, and to guide you to the correct gate.</p>
<h2 id="heading-multi-layered-access-control">Multi-layered Access Control</h2>
<p>To ensure comprehensive protection, access control should be enforced at multiple layers, depending on your application architecture.</p>
<p>To understand this, here’s a little something for my fellow Potter-heads:</p>
<h3 id="heading-hogwarts-in-harmony-a-unified-defense">Hogwarts in Harmony: A Unified Defense</h3>
<p>At the very edge of Hogwarts, you’ve got your Perimeter—the outer defenses that keep dark forces at bay. Think of these as the high, <em>enchanted stone walls</em> that surround the castle—acting like a firewall, with winged boar statues perched on the parapets, keeping watch. Only those with proper clearance are allowed through the gates, ensuring that no unwanted guests, like dark wizards, can enter.</p>
<p>When students arrive at Hogwarts, they come by <em>boats or Thestral-pulled carriages</em>, which are the only trusted means of transport. This is like <strong>Endpoint Detection and Response (EDR)</strong>, ensuring that only the right devices (or carriages) are allowed entry.</p>
<p>If a student tries to use a non-compliant device (like a <em>cursed broomstick or Apparition</em>), they won’t be allowed inside. <strong>Mobile Device Management (MDM)</strong> acts like the magical inspection process—only devices that meet Hogwarts' standards can pass through the gate and connect to the school’s systems.</p>
<p>At Hogwarts, <em>owls</em> are the trusted messengers that carry messages between the school and the outside world. These owls, like API keys and JWTs, carry the seal of approval and only deliver messages to the right recipients. Dark creatures like <em>Dementors</em> are forbidden from delivering messages, ensuring that only the right communications make it through.</p>
<p>The <em>Acceptance Letter from Hogwarts</em> is like an <strong>OAuth token</strong>. It proves you belong to the magical world and grants you access to the school without needing to show your face or reveal your blood status.</p>
<p>Inside the castle, access to different areas is controlled by who you are and your role at Hogwarts. For example, <strong>Role-Based Access Control (RBAC)</strong> ensures that only <em>Gryffindors</em> can access their common room, while <em>Slytherins</em> have their own. <em>Prefects</em> get additional privileges, like access to the Prefect's bathroom or other special rooms. These roles define where you can go and what you can do within the castle.</p>
<p>But things get more nuanced with <strong>Attribute-Based Access Control (ABAC)</strong>. For instance, only students enrolled in <em>Care of Magical Creatures</em> have access to the Forbidden Forest, but they’re only allowed in during daylight hours, when it's safer. The forest is too dangerous at night, and only those with the right attributes (like a specific timetable) can enter at the right time.</p>
<p>Within Hogwarts is the <em>Philosopher’s Stone</em>, hidden away in a vault guarded by powerful enchantments. This is your Data Layer – the most precious resources, secured by powerful protections. Just like database permissions, the vault is protected by Fluffy, the three-headed dog, a series of enchantments, and traps. Similarly, row-level and column-level security ensure that only Harry Potter can retrieve the Stone because he is the only one worthy (you can only access what’s meant for you).</p>
<p>To summarize,</p>
<ol>
<li><p><strong>Network Layer (Infrastructure-level):</strong> Firewalls and virtual private networks (VPNs) to control incoming and outgoing network traffic.</p>
</li>
<li><p><strong>Endpoint Layer (Device-level):</strong> Endpoint Detection and Response (EDR) and Mobile Device Management (MDM) to ensure only compliant device can access your application.</p>
</li>
<li><p><strong>API Layer (Service-level):</strong> API keys, JSON Web Tokens (JWTs), and API gateways to authenticate and authorize the caller and enforce policies such as rate limiting, IP whitelisting, and so on.</p>
</li>
<li><p><strong>Application Layer:</strong> Where the core business logic for authorization typically resides (which this guide is all about).</p>
</li>
<li><p><strong>Data Layer (Database-level):</strong> Database permissions, row/column-level security.</p>
</li>
</ol>
<h2 id="heading-access-control-models">Access Control Models</h2>
<p>At the application layer, three primary models of access control are commonly used in software engineering: Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), and the more recent Relationship-Based Access Control (ReBAC).</p>
<p><strong>RBAC</strong> <strong>(Role-Based Access Control)</strong> is a model where access is granted or denied based on the roles assigned to a user.</p>
<p>A role is a collection of permissions or privileges that define what actions a user can perform within a system. Roles simplify access control by assigning users to predefined roles, rather than managing individual permissions for each user.</p>
<p>When a user is assigned a role, they automatically inherit all the permissions associated with that role. Each permission also has a scope, which defines the boundaries or contexts within which the role's permissions apply. Scopes are typically used to restrict access to specific resources or data.</p>
<p>Let me illustrate this (and all concepts throughout this guide) using a blogging application as an example. This app allows users to create, manage, and publish blog posts in multiple categories. It supports a variety of user roles, each with different levels of access to the content and functionality within the platform.</p>
<ul>
<li><p><strong>Admin</strong>: Can view, edit, delete, and manage all blog posts and user roles. (Scope: All posts and users)</p>
</li>
<li><p><strong>Editor</strong>: Can edit and approve posts within their assigned categories (for example, Tech, Lifestyle). (Scope: Assigned categories)</p>
</li>
<li><p><strong>Author</strong>: Can create and edit only their own blog posts. (Scope: Own posts)</p>
</li>
<li><p><strong>Guest User</strong>: Can view public, published blog posts but cannot access private posts. (Scope: Public published posts only)</p>
</li>
</ul>
<p>The relationship between users and roles is often many-to-many, and roles may also be hierarchical, allowing for complex permission structures.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737780482515/e30316f8-58a9-4595-81ba-8eb08b2d5a3d.jpeg" alt="Role-based Access Control diagram" class="image--center mx-auto" width="5263" height="3039" loading="lazy"></p>
<p><strong>ABAC</strong> <strong>(Attribute-Based Access Control)</strong> is a model where access decisions are made based on the attributes of the subject (user), object (resource), and the environment. It dynamically evaluates whether a subject can perform an action on an object based on these attributes and policies that govern them.</p>
<p><strong>ReBAC</strong> <strong>(Relationship-Based Access Control)</strong> is an emerging model that grants access based on the relationships between users and resources. For example, it might allow only the user who created a post to edit it. This model is particularly useful in social networking applications, where access depends on user relationships (such as friends, followers, or content ownership).</p>
<h2 id="heading-why-abac">Why ABAC?</h2>
<p>RBAC provides several benefits, including ease of implementation, reduced administrative overhead by enabling quick onboarding of new users, and simplified auditing, as it makes it easy to review which roles have access to sensitive data.</p>
<p>But, as the platform grows, you introduce more nuanced requirements for access control. These new requirements lead to the creation of new roles to meet specific access needs:</p>
<ol>
<li><p><strong>Publisher</strong>: Can view, edit, approve, publish, and delete posts across all categories, but cannot manage user roles or settings.</p>
</li>
<li><p><strong>Junior Author</strong>: Can create and edit their own posts within assigned categories.</p>
</li>
<li><p><strong>Senior Author</strong>: Can create and edit their own posts in any category.</p>
</li>
<li><p><strong>User (Subscriber)</strong>: Can view and comment on private posts in addition to public posts.</p>
</li>
<li><p><strong>Premium Subscriber</strong>: Has all the permissions of a regular subscriber and access to exclusive posts.</p>
</li>
</ol>
<p>Before long, you may find yourself managing an ever-growing list of roles such as Senior Publisher, Publishing Supervisor, Guest User, Subscriber, Premium Subscriber, Graphic Designer, UX Designer, Photographer, Social Media Manager, US Marketing Specialist, UK Marketing Specialist, Web Developer, Data Analyst, Membership Manager, Ad Manager, Legal Advisor, and Sponsor Manager.</p>
<p>Introducing additional requirements—such as blog category, seniority, and jurisdiction—can quickly lead to role explosion. Just imagine how this would scale in data-intensive enterprise applications like finance or healthcare.</p>
<p>While scopes work well when boundaries are clear and static (for example, department, blog types), they require custom checks for more granular attributes such as seniority, length of service, blog creation time, or publication status. Scopes also struggle to account for attributes that change over time, like the location or timing of access.</p>
<p>Because RBAC relies on roles and fixed scopes to make access decisions, it becomes limited in handling complex and dynamic access needs. That is why, <a target="_blank" href="https://en.wikipedia.org/wiki/OWASP"><strong>OWASP</strong> (Open Worldwide Application Security Project) recommends using <strong>ABAC</strong> or <strong>ReBAC</strong> over RBAC</a>, as they are more effective in implementing the principle of least privilege.</p>
<h2 id="heading-attribute-based-access-control-in-depth">Attribute-Based Access Control In Depth</h2>
<h3 id="heading-core-components">Core Components</h3>
<p>The core components of ABAC are:</p>
<p><strong>Attributes</strong>: Attributes are key-value pairs used to define the access context. Examples include:</p>
<ul>
<li><p><strong>User attributes</strong>: These describe the characteristics of the person requesting access, like role, department, age, clearance level, and so on. 💡 As you can see, role can be one of the attributes based on which access control decision is based. So, ABAC is essentially an extension of RBAC.</p>
</li>
<li><p><strong>Resource attributes</strong>: These describe the characteristics of the resources (such as files, databases, or services) being accessed. For example, owner, category, status, and so on.</p>
</li>
<li><p><strong>Action attributes</strong>: These define what actions are being requested by the user on the resource. For example, <code>read</code> access like view/open, <code>write</code> access like create/modify/delete, <code>execute</code> access like process/run, and so on.</p>
</li>
<li><p><strong>Environment attributes</strong>: These include contextual elements such as <code>time</code> or <code>location</code> that influence the decision-making process.</p>
</li>
</ul>
<p><strong>Policies</strong>: Policies are logical rules or statements that define which combinations of attributes allow or deny access. For instance, A publisher can <em>publish</em> approved posts in assigned categories during business hours.</p>
<h3 id="heading-how-does-abac-work">How does ABAC work?</h3>
<p>Let’s take Sam, a publisher for our blog, as an example. Sam is authorized to publish posts that have been approved by the editor, but only within the categories she’s been assigned, such as ‘Tech,’ ‘Lifestyle,’ and ‘Health.’ She’s allowed to publish these posts only during specific hours, say from 9 AM to 6 PM.</p>
<p>Sam’s role as a publisher and her assigned categories were set when she joined the team. The resource here is the Post, which is given a category when it’s created. The action she can perform is to publish, and the environmental condition is that it needs to be during business hours.</p>
<p>Since the access control rule is based on Sam’s attributes—her role as a publisher and the categories she’s assigned to—she can publish posts within those categories. If any of her attributes change, like if she moves to a different department, such as Membership Management, or if her assigned categories change to ‘Fashion’ or ‘Travel,’ her access is automatically revoked.</p>
<blockquote>
<p><em>ABAC allows administrators to set access controls without needing to know who specifically will need access. As new members join an organization, there's no need to modify existing rules or object attributes; as long as they have the necessary attributes, they can access the required resources. This ability to automatically accommodate new and unanticipated users without additional adjustments is a key advantage of using ABAC</em>. (<a target="_blank" href="https://www.optiq.ai/blog-post/what-is-attribute-based-access-control-explained">Source</a>)</p>
</blockquote>
<h3 id="heading-who-defines-abac-policies">Who defines ABAC policies?</h3>
<ol>
<li><p><strong>Identity and Access Management administrators</strong>:</p>
<p> In many organizations, security administrators or access control administrators define ABAC policies. Their responsibilities include analyzing business needs, risk management, regulatory compliance, and ensuring that users have the right level of access to resources. They translate security requirements into policies based on the different attributes and conditions specific to the organization.</p>
</li>
<li><p><strong>Business and resource managers</strong>:</p>
<p> In certain cases, business units or department managers may also have input into defining policies. They understand the operational needs and are best positioned to indicate how data should be accessed by their teams.</p>
<p> For example, a Membership Manager might define policies governing who can access premium blog posts based on subscription status, user role, or membership level (e.g., Subscriber, Premium Subscriber).</p>
</li>
</ol>
<h3 id="heading-where-should-you-enforce-it-back-end-or-front-end"><strong>Where should you enforce it — back-end or front-end?</strong></h3>
<p>Access control policies should be enforced in <strong>both</strong> the front-end and the back-end. Here's why:</p>
<p><strong>1.</strong> <strong>Front-end enforcement</strong></p>
<ul>
<li><p><strong>Instant feedback</strong>: When you enforce ABAC policies on the front-end, you can immediately show or hide elements (like buttons, links, or menus) based on the user’s attributes. This makes the interface cleaner and helps users understand what they can or can’t do right away.</p>
</li>
<li><p><strong>Smarter UI</strong>: You can prevent showing options to users that they shouldn’t see. For example, hiding features if the user doesn’t have the correct role or permissions. This makes the UI feel more intuitive and responsive.</p>
</li>
<li><p><strong>Reduced server load</strong>: By enforcing certain access restrictions in the front-end, you reduce unnecessary requests to the back-end, improving app performance and reducing load on your servers.</p>
</li>
<li><p><strong>Security layer</strong>: While the front-end isn’t where sensitive data should live, you can still add an extra layer of security by using it to filter out invalid actions or content <strong>before</strong> a request is made to the back-end. For instance, you can hide sensitive UI elements (like admin controls) or disable buttons based on user attributes, making it harder for unauthorized users to even attempt to trigger certain actions.</p>
</li>
</ul>
<p><strong>2.</strong> <strong>Back-end enforcement</strong></p>
<ul>
<li><p><strong>Bypass risk</strong>: The downside of relying only on the front-end is that users can easily <strong>bypass</strong> it. With the right tools, they can manipulate the front-end code or network requests (using browser dev tools or API proxies). This is why back-end enforcement is essential—it ensures that access rules are applied <strong>server-side</strong>, where they can’t be tampered with.</p>
</li>
<li><p><strong>Protecting sensitive data</strong>: The back-end is where your sensitive data is stored and processed. By enforcing ABAC policies on the server, you ensure that unauthorized users can’t access, modify, or even view sensitive information. To avoid data leaks, you should always filter-out sensitive content based on user permissions and send only relevant content to the client.</p>
</li>
</ul>
<p>Now that you know ABAC policies need to be enforced both in the front-end and the back-end, the next question is: <strong>Where do you define these policies?</strong></p>
<p>As a developer, you might think: "<em>If I know the policies defined by the security team, I can just translate them into code for both the front-end and back-end.</em>"</p>
<p>For example, if the policy is that only senior authors can approve blogs in specific categories, you might write something like this:</p>
<p><strong>Front-end example (simplified):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (user.role === <span class="hljs-string">'author'</span> &amp;&amp; user.seniority === <span class="hljs-string">'senior'</span> &amp;&amp; user.categories.includes(<span class="hljs-string">'Tech'</span>)) {
  showApprovalDashboard();
} <span class="hljs-keyword">else</span> {
  hideApprovalDashboard();
}
</code></pre>
<p><strong>Back-end example (simplified):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (user.role === <span class="hljs-string">'author'</span> &amp;&amp; user.seniority === <span class="hljs-string">'senior'</span> &amp;&amp; user.categories.includes(<span class="hljs-string">'Tech'</span>)) {
  <span class="hljs-keyword">return</span> res.send(approvalDashboardData);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).send(<span class="hljs-string">'Forbidden: You do not have approval access for this category.'</span>);
}
</code></pre>
<p>But how do you ensure policy consistency across both layers of your application without duplicating logic?</p>
<p>What happens when you need to introduce additional conditions to this policy, like factoring in other user attributes or combining permissions with feature flags to conditionally enable certain features for specific users?</p>
<p>And, what if your requirement varies for each user like:</p>
<ul>
<li><p>Display certain UI elements only for users with a premium subscription,</p>
</li>
<li><p>Block an API call for a social media manager based on specific attributes,</p>
</li>
<li><p>Or hide an entire route for users who are not admins?</p>
</li>
</ul>
<p>Without a structured approach, your app becomes a tangled mess of if-else statements scattered across the codebase.</p>
<p>Read on to find the answers to these questions!</p>
<h3 id="heading-where-are-policies-defined">Where are policies defined?</h3>
<p>Before we dive into the implementation details, let me briefly revisit the question from the previous section: Where should you <em>define</em> the policies?</p>
<p>When there are multiple ways to access a service – whether through a mobile app, web app, or other platforms – the back-end should serve as the source of truth for policy definitions. Defining ABAC policies in the back-end keeps things consistent and secure across all platforms. This means that all clients interact with the same set of rules, reducing the chances of policy discrepancies.</p>
<p>So, the back-end is where all the policy definitions live, and it makes them available to the front-end when needed. The front-end then enforces these decisions on the user interface. But don't forget, the back-end should always enforce these policies as well.</p>
<p>In the upcoming sections, you will learn two approaches to implementing the ABAC access control model.</p>
<h2 id="heading-1-implementing-permissions-with-casl">1: Implementing Permissions with CASL</h2>
<p><a target="_blank" href="https://casl.js.org/v6/en">CASL</a> is an open-source, isomorphic JavaScript library that makes managing permissions in your app much easier with its simple, declarative API.</p>
<p>What this means is that you can use CASL on both the client-side (front-end) and server-side (back-end). This is especially great for full-stack applications, as it ensures consistency in access control. The same permission logic can be applied across your entire app, no matter where the request is coming from.</p>
<p>With CASL, you get <strong>declarative access control</strong>, which means you define <em>what</em> is allowed, rather than worrying about <em>how</em> to check permissions. This makes your code cleaner, more readable, and easier to maintain. Whether you're hiding UI elements in the front-end or making sure an API call is authorized in the back-end, CASL helps you enforce permissions consistently across your app.</p>
<p>The best part? You can define permissions using a clear, expressive syntax. This makes it easy to manage even complex permission rules. For example, you can control what a user can (or cannot) do based on their role, the resources they own, and other factors.</p>
<p>And it’s not just for React/React Native – they provide supporting packages for <a target="_blank" href="https://casl.js.org/v6/en/package/casl-angular">Angular</a>, <a target="_blank" href="https://casl.js.org/v6/en/package/casl-vue">Vue</a> and <a target="_blank" href="https://casl.js.org/v6/en/package/casl-aurelia">Aurelia</a> too.</p>
<h3 id="heading-step-1-install-casl">Step 1: Install CASL</h3>
<p>First, install CASL using a package manager. I have used v6 for the code examples.</p>
<pre><code class="lang-bash">npm install @casl/react @casl/ability
<span class="hljs-comment"># or</span>
yarn add @casl/react @casl/ability
<span class="hljs-comment"># or</span>
pnpm add @casl/react @casl/ability
</code></pre>
<h3 id="heading-step-2-define-the-abilities">Step 2: Define the abilities</h3>
<p>In CASL, think of "abilities" as a set of rules that define what actions a user can or cannot perform on specific subjects (like "Posts" or "Users"). Let’s use our earlier examples from the blogging application. For simplicity, we’ll consider two types of users: <strong>Admins</strong> and <strong>Authors</strong>.</p>
<ul>
<li><p>An Admin can manage everything.</p>
</li>
<li><p>An Author can create and edit their own posts within assigned categories, but they cannot delete published posts.</p>
</li>
</ul>
<p>Now, create a <code>defineAbilities.ts</code> file to define the abilities in a high-level, declarative manner using DSL.</p>
<p>Start by defining the <code>Actions</code> that a user can perform (for example, <code>create</code>, <code>read</code>, <code>update</code>, <code>delete</code>, <code>manage</code>) and the <code>Subjects</code> (the entities that actions are performed on, such as <code>‘User’</code>, <code>‘Post‘</code>, or objects like <code>User</code> or <code>Post</code>).</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//defineAbilities.ts</span>

<span class="hljs-keyword">type</span> Actions = <span class="hljs-string">'create'</span> | <span class="hljs-string">'read'</span> | <span class="hljs-string">'update'</span> | <span class="hljs-string">'delete'</span> | <span class="hljs-string">'manage'</span>;
<span class="hljs-keyword">type</span> Subjects = <span class="hljs-string">'User'</span> | <span class="hljs-string">'Post'</span> | <span class="hljs-string">'all'</span> | User | Post
</code></pre>
<p>Then, create a type representing the structure of your abilities. It combines the <code>Actions</code> and <code>Subjects</code> to create a clear and type-safe ability system.</p>
<p>The <code>PureAbility&lt;[Actions, Subjects]&gt;</code> means that the ability system will know what actions are allowed on which subjects. The <code>createAppAbility</code> function is used to create an ability instance based on your defined actions and subjects. You can use this function to create abilities specific to a user’s role or permissions.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//defineAbilities.ts</span>

<span class="hljs-keyword">import</span> { CreateAbility, PureAbility, AbilityBuilder, createMongoAbility } <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/ability'</span>;
<span class="hljs-comment">// other imports</span>

<span class="hljs-keyword">type</span> Actions = <span class="hljs-string">'create'</span> | <span class="hljs-string">'read'</span> | <span class="hljs-string">'update'</span> | <span class="hljs-string">'delete'</span> | <span class="hljs-string">'manage'</span>;
<span class="hljs-keyword">type</span> Subjects = <span class="hljs-string">'User'</span> | <span class="hljs-string">'Post'</span> | <span class="hljs-string">'all'</span> | Post | User

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> AppAbility = PureAbility&lt;[Actions, Subjects]&gt;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> createAppAbility = createMongoAbility <span class="hljs-keyword">as</span> CreateAbility&lt;AppAbility&gt;
</code></pre>
<p>Note that <code>createMongoAbility</code> is only used to support simple operators from <a target="_blank" href="https://www.mongodb.com/docs/manual/reference/operator/query/">MongoDB Query Language</a>, like $in, $lte, $eq that are used to specify conditions for your rules. Don't worry – this doesn't mean your app has to use MongoDB, nor do you need to be familiar with the query language. You can also skip these entirely and create custom operators.</p>
<p>Next, define a function called <code>defineAbilityFor</code>, which takes a <code>user</code> object as its argument and returns an ability instance. The <code>user</code> object is expected to have a <code>role</code> property (such as 'admin' or 'author') that determines the user's permissions.</p>
<p>The <code>userPermissions</code> object maps each user to a function that defines their permissions using the <code>can</code> and <code>cannot</code> methods provided by <code>AbilityBuilder</code>. This approach scales better than a switch case as you add more roles.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//defineAbilities.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defineAbilityFor</span>(<span class="hljs-params">user: User</span>) </span>{
  <span class="hljs-keyword">const</span> { can, cannot, build } = <span class="hljs-keyword">new</span> AbilityBuilder(createAppAbility);
   <span class="hljs-keyword">const</span> userPermissions = {
    admin: <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Admin user can manage everything</span>
      can(<span class="hljs-string">'manage'</span>, <span class="hljs-string">'all'</span>);
    },
    author: <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Author can create Posts but cannot delete them</span>
      can(<span class="hljs-string">'create'</span>, <span class="hljs-string">'Post'</span>);
      cannot(<span class="hljs-string">'delete'</span>, <span class="hljs-string">'Post'</span>);
    },
    <span class="hljs-comment">// Add more roles</span>
  };

  <span class="hljs-comment">// Call the permissions associated with the user, or default to no permissions.</span>
  <span class="hljs-keyword">const</span> permissions = userPermissions[user.role] || (<span class="hljs-function">() =&gt;</span> {});
  permissions(); 

  <span class="hljs-keyword">return</span> build();
}
</code></pre>
<p>Note: <code>manage</code> and <code>all</code> are keywords in CASL where manage means any action and all means any subject.</p>
<p>To specify conditions that prevent users from updating posts they haven't created, deleting published posts, and to restrict access to certain fields, you can use <strong>conditions</strong> and <strong>fields</strong>. CASL allows you to set specific conditions on permissions via the <code>subject</code> property, which represents the object, and the <code>fields</code> property, which represents the object’s properties that the user is interacting with.</p>
<p>Add conditional rules to the above file.</p>
<pre><code class="lang-typescript">
   author: <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Author can create posts in the 'Tech' and 'Lifestyle' categories</span>
      can(<span class="hljs-string">'create'</span>, <span class="hljs-string">'Post'</span>, { category: { $in: [<span class="hljs-string">'Tech'</span>, <span class="hljs-string">'Lifestyle'</span>] } });

      <span class="hljs-comment">// Author can update the title and description of posts authored by the user</span>
      can(<span class="hljs-string">'update'</span>, <span class="hljs-string">'Post'</span>, [<span class="hljs-string">'title'</span>, <span class="hljs-string">'description'</span>], { ownerId: user.id, status: <span class="hljs-string">'draft'</span> });

      <span class="hljs-comment">// Author cannot delete posts that have a 'Published' status</span>
      cannot(<span class="hljs-string">'delete'</span>, <span class="hljs-string">'Post'</span>, { status: <span class="hljs-string">'published'</span> });
    },
</code></pre>
<p>In CASL, direct rules (like <code>can</code>) are combined using <code>OR</code> and inverted rules (like <code>cannot</code>) and conditions are combined using <code>AND</code>. The author:</p>
<ul>
<li><p>can create Posts in their assigned categories <code>OR</code></p>
</li>
<li><p>can update title/description of the Posts that they own <code>AND</code> are in Draft state</p>
</li>
<li><p><code>AND</code> cannot delete published Posts</p>
</li>
</ul>
<p>Remember, for the same action/subject pair, you should define <code>cannot</code> rules <em>after</em> <code>can</code> rules, else they will be overridden.</p>
<p>When dealing with a <code>Post</code> object that has a nested <code>details</code> field (for example, <code>details.author.name</code>, <code>details.metadata.tags</code>), you can use the <code>*</code> and <code>**</code> wildcards to control access based on the level of nesting.</p>
<ul>
<li><p>The <code>*</code> wildcard matches only the <strong>top-level fields</strong> within a given object.</p>
<p>  This means it will grant access to fields that are directly inside the <code>details</code> object, but not any <strong>nested fields</strong>.</p>
</li>
<li><p>The <code>**</code> wildcard allows access to <strong>all fields</strong>, including deeply nested ones, within the object.</p>
<p>  This means it will grant access to every field inside <code>details</code>, regardless of how deep the nesting goes.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// gives access to all nested fields under Post.details, no matter how deep they are</span>
can(<span class="hljs-string">'read'</span>, <span class="hljs-string">'Post'</span>, [<span class="hljs-string">'details.**'</span>]) 

<span class="hljs-comment">// give access to only the top level fields (such as details.body, details.author)</span>
can(<span class="hljs-string">'read'</span>, <span class="hljs-string">'Post'</span>, [<span class="hljs-string">'details.*'</span>])
</code></pre>
<p>Note that <code>*</code> matches all symbols except dot (.)</p>
<p>The ability instance in <code>defineAbilities.ts</code> can be used to enforce permissions across your app. This file can act as a shared library, so both the front-end (for example: React) and back-end (for example: Node.js) can access and use the same permission logic.</p>
<p>While the <code>AbilityBuilder</code> works for permissions defined inside the system, if your application receives externally defined permissions as a JSON object, like:</p>
<pre><code class="lang-json">[
  {
    action: 'read',
    subject: 'Post'
  },
  {
    inverted: <span class="hljs-literal">true</span>, <span class="hljs-comment">// indicates cannot rules</span>
    action: 'delete',
    subject: 'Post',
    conditions: { published: <span class="hljs-literal">true</span> }
  }
]
</code></pre>
<p>you can pass it directly into the <code>Ability</code> constructor as follows:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> defineAbilityFor = (permissions: <span class="hljs-function">(<span class="hljs-params">SubjectRawRule&lt;<span class="hljs-built_in">any</span>, <span class="hljs-built_in">any</span>, MongoQuery&lt;AnyObject&gt;&gt;</span>)[]) =&gt;</span> {
    <span class="hljs-keyword">return</span> createMongoAbility&lt;[Actions, Subjects]&gt;(permissions);
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineAbilityFor;
</code></pre>
<p>Using JSON to define rules also has the added advantage of <strong>reducing your app's bundle size</strong> since you don't need to include heavy dependencies like <code>AbilityBuilder</code>!</p>
<h3 id="heading-step-3-create-ability-instance-for-the-user"><strong>Step 3: Create ability instance for the user</strong></h3>
<p>After successful authentication by your Login or Authentication service, you’ll fetch the user data or associated permissions (depending on the approach you choose in step 2) to your app and create an ability instance in your login component (or similar) as follows:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// login.tsx</span>

<span class="hljs-keyword">import</span> defineAbiltyFor <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/defineAbilities.js'</span>

<span class="hljs-keyword">const</span> LoginComponent = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Get user data from API. Then,</span>
    <span class="hljs-keyword">const</span> ability = defineAbilityFor(user)
}
</code></pre>
<h3 id="heading-step-4-provide-ability-instance-to-the-entire-app"><strong>Step 4: Provide ability instance to the entire app</strong></h3>
<p><a target="_blank" href="https://react.dev/reference/react/createContext">Contexts</a> are used in React to share data across components without having to pass props through the component tree. Add the below code in a <code>can.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// can.ts</span>

<span class="hljs-keyword">import</span> {createContext} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> {createContextualCan} <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AbilityContext = createContext()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Can = createContextualCan(AbilityContext.Consumer)
</code></pre>
<p>This creates a <code>Can</code> component, which you will use in the next step to determine if a user has permissions to perform an action, based on the abilities passed through <code>AbilityContext</code>.</p>
<p>Next, use the above <code>AbilityContext</code> to wrap your <code>App</code> component and set the <code>ability</code> instance created in step 3 as the <code>value</code>, so that the abilities are available to all the components in the application.</p>
<pre><code class="lang-typescript">ReactDOM.render(
&lt;AbilityContext.Provider value={ability}&gt;
  &lt;App /&gt;
&lt;/AbilityContext.Provider&gt;,
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)
)
</code></pre>
<h3 id="heading-step-5-check-user-permission-using-abilities"><strong>Step 5: Check user permission using abilities</strong></h3>
<p>There are two ways to determine if a user has permission to perform an action: using <code>ability.can</code> for programmatic checks and using the <code>Can</code> component for conditional rendering.</p>
<p>Assume this is your post object:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// post.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Post {
    ownerId: <span class="hljs-built_in">string</span>;
    category: <span class="hljs-built_in">string</span>;
    title: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    status: <span class="hljs-built_in">string</span>;
}
<span class="hljs-keyword">const</span> post: Post = {
    ownerId: <span class="hljs-string">'yourUserName'</span>,
    category: <span class="hljs-string">'Lifestyle'</span>,
    title: <span class="hljs-string">'My First Post'</span>,
    description: <span class="hljs-string">'This is the description for the first post.'</span>,
    status: <span class="hljs-string">'published'</span>
};
</code></pre>
<p>Both <code>ability.can</code> and the <code>Can</code> component take action, subject, and an optional field and check these parameters against the defined abilities.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// user-profile.tsx</span>

<span class="hljs-keyword">import</span> { useAbility } <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/react'</span>;
<span class="hljs-keyword">import</span> { subject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/ability'</span>;
<span class="hljs-keyword">import</span> { AbilityContext, Can } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/can'</span>;
<span class="hljs-comment">// other imports</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">const</span> UserProfile = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> ability = useAbility(AbilityContext);

  <span class="hljs-keyword">const</span> canCreatePost = ability.can(<span class="hljs-string">'create'</span>, <span class="hljs-string">'Post'</span>); <span class="hljs-comment">//==== Example (1) ====</span>
  <span class="hljs-keyword">const</span> canDeletePost = ability.can(<span class="hljs-string">'delete'</span>, post); <span class="hljs-comment">//==== Example (2) ====</span>

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;h1&gt;User Profile&lt;/h1&gt;

      {<span class="hljs-comment">/* ==== Example (3) ==== */</span>}
      &lt;Can I=<span class="hljs-string">"delete"</span> a=<span class="hljs-string">"Post"</span>&gt;
        &lt;p&gt;You can <span class="hljs-keyword">delete</span> a Post.&lt;/p&gt;
      &lt;/Can&gt;

      {<span class="hljs-comment">/* ==== Example (4) ==== */</span>}
      &lt;Can I=<span class="hljs-string">"delete"</span> <span class="hljs-built_in">this</span>={subject(<span class="hljs-string">'Post'</span>, post)}&gt;
        {<span class="hljs-function">(<span class="hljs-params">allowed</span>) =&gt;</span>
          allowed ? &lt;button disabled={!allowed}&gt;Delete Post&lt;/button&gt; 
          : &lt;p&gt;Cannot <span class="hljs-keyword">delete</span> post.&lt;/p&gt;
        }
      &lt;/Can&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>See how readable the permission check is?</p>
<p>Now look at the four examples.</p>
<p>Example <code>(1)</code> returns true because user can create posts.</p>
<p>Example <code>(2)</code> should return true because you can delete your published posts, <strong>but it returns</strong> <strong>false</strong>. Why? Because even though <code>post</code> is an instance of <code>Post</code>, CASL cannot detect its subject type (type of <code>post</code> object) as CASL uses <code>object.constructor.modelName</code> or <code>object.constructor.name</code> for subject type detection.</p>
<p>You have two ways to fix this.</p>
<ul>
<li><p>Use a <code>subject</code> helper to specify the type of <code>post</code> instance as shown in example <code>(4)</code> (it returns true)</p>
</li>
<li><p>Use a custom subject type detection algorithm to state which property CASL needs to use to discern the type. This can be done using <code>detectSubjectType</code> like this:</p>
<pre><code class="lang-typescript">  <span class="hljs-comment">// defineAbilities.ts</span>

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defineAbilityFor</span>(<span class="hljs-params">user: User</span>) </span>{
    <span class="hljs-keyword">const</span> { can, cannot, build } = <span class="hljs-keyword">new</span> AbilityBuilder(createAppAbility);
    <span class="hljs-comment">// rules defined as explained above</span>

    <span class="hljs-keyword">return</span> build({
      detectSubjectType: <span class="hljs-function"><span class="hljs-params">object</span> =&gt;</span> <span class="hljs-built_in">object</span>.__typename
    });
  }

   <span class="hljs-comment">// post.ts</span>

   <span class="hljs-keyword">const</span> post: Post = {
      ownerId: <span class="hljs-string">'yourUserName'</span>,
      category: <span class="hljs-string">'Lifestyle'</span>,
      title: <span class="hljs-string">'My First Post'</span>,
      description: <span class="hljs-string">'This is the description for the first post.'</span>,
      status: <span class="hljs-string">'published'</span>,
      __typename: <span class="hljs-string">'Post'</span>
  };
</code></pre>
</li>
</ul>
<p>Now, example <code>(2)</code> should return true.</p>
<p>Next, look at example <code>(3)</code>. It also returns true because the check is on subject <em>type</em> and not on the subject. Remember, when you check on a</p>
<blockquote>
<ul>
<li><p>subject, you ask "can I delete THIS post?"</p>
</li>
<li><p>subject type, you ask "can I delete SOME article?" (that is, at least one post) (<a target="_blank" href="https://casl.js.org/v6/en/guide/intro">Source</a>)</p>
</li>
</ul>
</blockquote>
<p>While CASL offers a powerful approach to granular access control, it doesn’t directly address our requirement to apply conditions based on user attributes.</p>
<p>Although third-party libraries can provide convenience, their documentation is sometimes unclear, outdated, or inaccurate, and there may be vulnerabilities within the components themselves. For complete control over your security processes, implementing custom authorization logic may be necessary.</p>
<h2 id="heading-2-build-your-custom-permissions-validation-framework">2: Build Your Custom Permissions Validation Framework</h2>
<p>To build a custom validation framework, let’s look into how the policies are defined, validated, and enforced and see how all these pieces come together.</p>
<h3 id="heading-policy-definition-using-policy-as-code"><strong>Policy Definition using Policy as Code</strong></h3>
<p>You have already learned that your access control policies should reside in the back-end. For the custom implementation, you will be using <strong>Policy as Code</strong> or PaC. This refers to the practice of defining and managing policies using code or configuration files (like YAML, JSON or DSL) rather than manual processes or documentation. This allows policies to be version-controlled, automatically enforced, and more reliable in dynamic environments. These policies are authored by the security admin and are managed by a Policy Service.</p>
<p>In YAML, your policy may look like this, where the <code>policies</code> list is represented by a sequence (<code>-</code>).</p>
<pre><code class="lang-yaml"><span class="hljs-attr">policies:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">policyId:</span> <span class="hljs-string">P001</span>
    <span class="hljs-attr">resource:</span> <span class="hljs-string">Post</span>
    <span class="hljs-attr">action:</span> <span class="hljs-string">view</span>
    <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
    <span class="hljs-attr">conditions:</span> <span class="hljs-string">'(resource.tag != "exclusive") || (resource.tag == "exclusive" &amp;&amp; user.role == "premium user")'</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">policyId:</span> <span class="hljs-string">P002</span>
    <span class="hljs-attr">resource:</span> <span class="hljs-string">Post</span>
    <span class="hljs-attr">action:</span> <span class="hljs-string">edit</span>
    <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
    <span class="hljs-attr">conditions:</span> <span class="hljs-string">'resource.ownerId == user.id'</span>
  <span class="hljs-comment"># other policies</span>
</code></pre>
<p>The <strong>policyId</strong> is a unique identifier for the policy. The <strong>resource</strong> specifies the type of resource the policy applies to, such as "Post." The <strong>action</strong> defines what operation is allowed or denied on the resource, like "edit." The <strong>effect</strong> determines whether the action is allowed or denied, with values like "allow" or "deny." The <strong>conditions</strong> represent the logical expression that must be satisfied for the policy to apply, such as checking if the resource's owner ID matches the user's ID.</p>
<p>As you can see, the conditions in the policies are in a TypeScript-like, human-readable format. This is because they are written using Google's <strong>Common Expression Language (CEL)</strong>.</p>
<p>CEL is an open-source, platform-independent language that is fast and safe for executing user-defined expressions (<a target="_blank" href="https://owasp.org/www-community/attacks/Direct_Dynamic_Code_Evaluation_Eval%20Injection">unlike <code>eval()</code></a>, especially on the server-side). Its performance is enhanced because CEL is compiled once into an abstract syntax tree, which is then used to evaluate against multiple inputs in nanoseconds or microseconds.</p>
<p>Let’s redefine the structure as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">policies:</span>
  <span class="hljs-attr">Post:</span>
    <span class="hljs-attr">view:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">P001</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Post</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">view</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'(resource.tag != "exclusive") || (resource.tag == "exclusive" &amp;&amp; user.role == "premium user")'</span>
    <span class="hljs-attr">edit:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">P002</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Post</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">edit</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'resource.ownerId == user.id'</span>
    <span class="hljs-attr">publish:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">P003</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Post</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">publish</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'user.role == "publisher" &amp;&amp; resource.category in ["Tech", "Lifestyle"] &amp;&amp; resource.status == "approved" &amp;&amp; system.time &gt;= "09:00:00" &amp;&amp; system.time &lt;= "18:00:00"'</span>

  <span class="hljs-attr">Comment:</span>
    <span class="hljs-attr">create:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">C001</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">create</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">deny</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'user.role == "guest"'</span>
    <span class="hljs-attr">edit:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">C002</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">edit</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'resource.authorId == user.id'</span>
    <span class="hljs-attr">delete:</span>
      <span class="hljs-attr">policyId:</span> <span class="hljs-string">C003</span>
      <span class="hljs-attr">resource:</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">action:</span> <span class="hljs-string">delete</span>
      <span class="hljs-attr">effect:</span> <span class="hljs-string">allow</span>
      <span class="hljs-attr">conditions:</span> <span class="hljs-string">'resource.authorId == user.id || user.role in ["moderator", "admin"]'</span>
  <span class="hljs-comment"># other policies</span>
</code></pre>
<p>Here’s why:</p>
<ol>
<li><p><strong>Improved Structure</strong>: By grouping policies by resource and action, you make it much easier to navigate. Adding new policies or actions becomes a breeze, without disrupting the overall setup. For example, if you need to add an <code>archive</code> action for the <code>Post</code> resource, you simply add it under the <code>Post</code> object. This modular approach makes maintaining and extending policies much simpler.</p>
</li>
<li><p><strong>Efficient Lookup</strong>: When these policies are accessed in your app as JavaScript objects, lookups are efficient and constant in time (O(1)). This is because policies are stored using direct key lookups, where each policy can be accessed instantly by its unique key. This significantly boosts performance compared to searching through a list (which would take O(n) time). As the number of policies grows, your lookup time stays the same, so performance doesn't slow down.</p>
</li>
<li><p><strong>Easier Auditing &amp; Version Control</strong>: This structure also makes auditing and version control much smoother. You can easily track changes to policies and manage updates without the risk of accidentally disrupting other policies.</p>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">To understand how string literals work in CEL for the above conditions, check out some examples <a target="_self" href="https://stackblitz.com/edit/github-b9k23yjf-kbho9jtj?file=demo.ts">here</a>.</div>
</div>

<h3 id="heading-workflow-overview">Workflow Overview</h3>
<p>When the application starts, you fetch policies from the Policy Service using RTK Queries, which automatically caches them in your RTK cache. Once the user is authenticated, their data—like role and department—will also be stored in the cache.</p>
<p>To persist this data for the duration of the session, you'll need to store it in session storage, but be mindful to avoid storing sensitive information. For the purposes of our permission validator, we'll read user data directly from the cache.</p>
<p>At points where policy enforcement is needed, such as in components or routes (let’s call these <em>policy enforcement points</em>), the application will call our custom permission hook. This hook then validates permissions based on the policies, the user, the resource, and the environment attributes to either grant or deny access to the requested action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737780571125/1dba1568-ee54-4bea-8d25-5c058fa6da68.jpeg" alt="Attribute-based Access Control Workflow" class="image--center mx-auto" width="5201" height="3076" loading="lazy"></p>
<h3 id="heading-policy-validation">Policy Validation</h3>
<h4 id="heading-step-1-create-a-permission-validator">Step 1: Create a permission validator</h4>
<p>Begin by defining the types for <code>Action</code>, <code>Resource</code>, and <code>Policy</code> in your code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// validator.type.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Action = <span class="hljs-string">"view"</span> | <span class="hljs-string">"edit"</span> | <span class="hljs-string">"create"</span> | <span class="hljs-string">"approve"</span> | <span class="hljs-string">"publish"</span> | <span class="hljs-string">"delete"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Resource = Partial&lt;Post&gt; | Partial&lt;User&gt; | Partial&lt;Comment&gt;;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> PolicyEffect = <span class="hljs-string">"allow"</span> | <span class="hljs-string">"deny"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Policy {
  policyId: <span class="hljs-built_in">string</span>;
  resource: <span class="hljs-built_in">string</span>;
  action: <span class="hljs-built_in">string</span>;
  effect: PolicyEffect;
  conditions: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>You might be wondering why you need to use <code>Partial</code> here. By using <code>Partial</code>, we’re saying that each field on <code>Post</code>, <code>User</code>, or <code>Comment</code> is not required when performing certain actions. This is particularly useful when you validate create actions, where the object may not be fully formed yet – some fields might still be missing. For example, when creating a new <code>Post</code>, you might only have a title and content, but not the full list of comments or tags.</p>
<p>Then, install <code>cel-js</code>, a CEL evaluator for JavaScript to be used in your validator.</p>
<pre><code class="lang-bash">npm i cel-js
</code></pre>
<p>Create a <code>validatePermission</code> function to pull the action rules for the given resource from the provided <code>policies</code> object and build a context that includes the <code>user</code>, <code>resource</code>, and <code>system</code> information. Note that you may have to use <code>__typename</code> (or similar) for resource type detection, similar to what you did in CASL.</p>
<p>Using the <code>cel-js</code> library, evaluate the <code>conditions</code> specified in the action rules, which will check if the user meets the required criteria for the action. If the conditions are satisfied, the policy "takes effect," meaning the specified action is enforced according to the defined effect – whether allowing or denying the action. If there are no rules defined or an error occurred during evaluation, deny by default.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// validator.ts</span>

<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> cel <span class="hljs-keyword">from</span> <span class="hljs-string">'cel-js'</span>;
<span class="hljs-comment">// other imports</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> validatePermission = (
  action: Action,  
  resource: Resource,  
  system: System, 
  user: User,
  policies: { [resourceKey: <span class="hljs-built_in">string</span>]: { [actionKey: <span class="hljs-built_in">string</span>]: Policy } }
): <span class="hljs-function"><span class="hljs-params">boolean</span> =&gt;</span> {

  <span class="hljs-keyword">const</span> actionRules = policies[resource.__typename]?.[action];
  <span class="hljs-keyword">if</span> (!actionRules) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; 

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> context = {
      user: user,  
      resource: resource,  
      system: system,  
    };

    <span class="hljs-keyword">return</span> cel.evaluate(actionRules.conditions, context) &amp;&amp; actionRules.effect === <span class="hljs-string">"allow"</span>;

  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error evaluating permission condition:'</span>, error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
};
</code></pre>
<p>Any component that needs to validate a user’s permission for an action requires fetching policies from the cache and retrieving the user from the global state, while also managing loading and error states.</p>
<p>To avoid this code duplication and encapsulate the logic for the above operations, you can create a custom hook that provides a consistent interface for permission validation across components.</p>
<h4 id="heading-step-2-create-a-custom-hook-to-encapsulate-reusable-logic">Step 2: Create a custom hook to encapsulate reusable logic</h4>
<p>Since the policies were already fetched from the policy management service during app startup, the same RTK Query will now retrieve them directly from the cache. Follow the below reference to create a <code>usePermission</code> custom hook.</p>
<p>Notice how the <code>skip: !userId</code> condition is used to ensure that the policies are only fetched if a valid <code>userId</code> is present, preventing unnecessary network requests.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// usePermission.ts</span>

<span class="hljs-keyword">import</span> { useSelector } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>;
<span class="hljs-keyword">import</span> { useGetPoliciesQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'./services/api'</span>; 
<span class="hljs-keyword">import</span> { validatePermission } <span class="hljs-keyword">from</span> <span class="hljs-string">'./validator'</span>;
<span class="hljs-comment">// other imports</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> usePermission = (action: Action, resource: Resource, system: System): <span class="hljs-function"><span class="hljs-params">boolean</span> =&gt;</span> {

  <span class="hljs-keyword">const</span> user = useSelector(<span class="hljs-function">(<span class="hljs-params">state: <span class="hljs-built_in">any</span></span>) =&gt;</span> state.user); 

  <span class="hljs-keyword">const</span> { data: policies, isLoading: isPoliciesLoading, isError: isPoliciesError } = useGetPoliciesQuery({
    skip: !userId,
  });

  <span class="hljs-keyword">if</span> (isPoliciesError || !policies) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to fetch policies'</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-keyword">const</span> hasPermission = validatePermission(action, resource, system, user, policies);

  <span class="hljs-keyword">return</span> hasPermission;
};
</code></pre>
<h4 id="heading-step-3-add-contextual-action-validation">Step 3: Add contextual action validation</h4>
<p>More often than not, even if a user has the required permission to perform an action, they still might not be allowed to do so because of contextual business logic. For example:</p>
<ul>
<li><p><strong>Post approval</strong>: An editor may have permission to approve a post, but if they’re in the middle of editing it and there are unsaved changes, the approve button should be hidden.</p>
</li>
<li><p><strong>Commenting</strong>: The comment button should be disabled if a user hasn’t typed anything, even if they have permission to comment.</p>
</li>
<li><p><strong>Category creation</strong>: A user with permission might still be blocked from creating a category if the name is empty or already exists.</p>
</li>
</ul>
<p>These rules depend on the current state of the application and need to be handled dynamically. To handle these contextual actions, the validation rules should be defined based on the current state of the application (for example, the post being edited, content being typed, category name availability).</p>
<p>Before delving into how custom hooks can handle these validations, let’s first lay out the rules for these contextual actions:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// contextualRules.ts</span>

<span class="hljs-keyword">import</span> _ <span class="hljs-keyword">from</span> <span class="hljs-string">'lodash'</span>;
<span class="hljs-comment">// other imports</span>

<span class="hljs-keyword">const</span> contextualActionRules = {
  Post: {
    approve: <span class="hljs-function">(<span class="hljs-params">state: PostState, resource: Resource</span>) =&gt;</span> {
      <span class="hljs-comment">// Prevent approval if the post is currently being edited</span>
      <span class="hljs-keyword">const</span> postId = resource?.id;
      <span class="hljs-keyword">return</span> postId &amp;&amp; !state[postId]?.isEditing;
    },
  },
  Comment: {
    create: <span class="hljs-function">(<span class="hljs-params">state: CommentState, resource: Resource</span>) =&gt;</span> {
      <span class="hljs-comment">// Prevent creating a comment if the comment content is empty</span>
      <span class="hljs-keyword">return</span> !_.isEmpty(resource?.content);
    },
  },
  Category: {
    create: <span class="hljs-function">(<span class="hljs-params">state: CategoryState[], resource: Resource</span>) =&gt;</span> {
      <span class="hljs-comment">// Prevent creating a category if the name is empty or already exists</span>
      <span class="hljs-keyword">const</span> categoryName = resource.name?.trim();
      <span class="hljs-keyword">return</span> (
        !_.isEmpty(categoryName) &amp;&amp;
        !state.some(<span class="hljs-function"><span class="hljs-params">category</span> =&gt;</span> category.name === categoryName)
      );
    },
  },
};
</code></pre>
<p>Now, update the <code>usePermission</code> hook to incorporate checks for <code>contextualActionRules</code>. If a contextual rule is defined for the specified <code>resource</code> and <code>action</code>, it will be evaluated alongside the policy-based permission using the current application <code>state</code>. If no contextual rule is found, the hook will return the result based solely on the policy-based permission.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// usePermission.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> usePermission = (action: Action, resource: Resource, system: System): <span class="hljs-function"><span class="hljs-params">boolean</span> =&gt;</span> {

  <span class="hljs-keyword">const</span> state = useSelector(<span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state);

  <span class="hljs-comment">/**
    This part of the code is same as above
  **/</span> 

  <span class="hljs-keyword">const</span> hasPermission = validatePermission(action, resource, system, user, policies);
  <span class="hljs-keyword">const</span> validateContextualRule = contextualActionRules[resource?.__typename]?.[action];

  <span class="hljs-keyword">if</span> (validateContextualRule) {
    <span class="hljs-keyword">const</span> contextualActionAllowed = validateContextualRule(state, resource);
    <span class="hljs-keyword">return</span> hasPermission &amp;&amp; contextualActionAllowed;
  }

  <span class="hljs-keyword">return</span> hasPermission;
};
</code></pre>
<p>There is one thing that most <strong>definitely</strong> needs to be changed in the above code. Take a guess?</p>
<p><strong>How is</strong> <code>usePermission</code> <strong>beneficial for contextual validations based on the app state?</strong> Because the hook is subscribed to the application state! So, when something changes – like typing into a comment box – the hook re-renders. Since the Comment component relies on this hook to control the comment button’s state, any update in the hook also triggers a re-render of the component. This means that as you type, the button becomes visible, and if the content is cleared, the button gets disabled.</p>
<p>But, we don’t want the <code>usePermission</code> hook to re-render <em>every</em> time the app state changes. Let’s fix that.</p>
<p>Define <code>resourceToStateMap</code> outside the <code>usePermission</code> hook to avoid redundant re-creation for every call. <code>useSelector</code> subscribes only to the relevant slice of state based on the resource type and ID.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Bad practice: Instead of this,</span>
<span class="hljs-keyword">const</span> state = useSelector(<span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state);

<span class="hljs-comment">// Good practice: Do this</span>
<span class="hljs-keyword">const</span> resourceToStateMap: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-function">(<span class="hljs-params">state: RootState, id: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span></span>) =&gt;</span> <span class="hljs-built_in">any</span>&gt; = {
  Post:     <span class="hljs-function">(<span class="hljs-params">state, id</span>) =&gt;</span> state.posts[id],
  Comment:  <span class="hljs-function">(<span class="hljs-params">state, id</span>) =&gt;</span> state.comments[id],
  User:     <span class="hljs-function">(<span class="hljs-params">state, id</span>) =&gt;</span> state.user,
  <span class="hljs-comment">// Add more </span>
};

<span class="hljs-keyword">const</span> resourceType = resource?.__typename;
<span class="hljs-keyword">const</span> resourceId = resource?.id;
<span class="hljs-keyword">const</span> stateSlice = useSelector(<span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (resourceType &amp;&amp; resourceId &amp;&amp; resourceToStateMap[resourceType]) {
    <span class="hljs-keyword">return</span> resourceToStateMap[resourceType](state, resourceId);
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
});
</code></pre>
<p>This is why it’s important to make selectors as granular as possible.</p>
<ul>
<li><p><strong>Avoid over-fetching</strong>: You’re not selecting the entire state anymore, just the piece of it that’s necessary for evaluating the permission and contextual rules. This is much more efficient, especially in large applications.</p>
</li>
<li><p><strong>Optimized re-renders</strong>: With granular state selection, only the relevant state slice will trigger a re-render, improving the performance of the application, especially when many components are using the <code>usePermission</code> hook.</p>
</li>
</ul>
<p>Now that you’ve completed the bulk of the permission validation logic, let’s make it prettier to use.</p>
<h4 id="heading-step-4-create-a-wrapper-for-conditional-rendering">Step 4: Create a wrapper for conditional rendering</h4>
<p>Create a <code>Can</code> component that checks if the user has permission to perform a specific action on a resource using the <code>usePermission</code> hook. If permission is granted, it renders the <code>children</code> or calls it as a function with the permission status (this will be used to disable buttons). If not, it displays a fallback element.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Can.tsx</span>

<span class="hljs-keyword">import</span> { usePermission } <span class="hljs-keyword">from</span> <span class="hljs-string">'../hooks/usePermission'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CanProps {
  I: Action;
  a: Resource;
  context: System;
  fallback?: React.ReactNode; 
  children: React.ReactNode | (<span class="hljs-function">(<span class="hljs-params">allowed: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> React.ReactNode); 
}

<span class="hljs-keyword">const</span> Can: React.FC&lt;CanProps&gt; = <span class="hljs-function">(<span class="hljs-params">{
  I,
  a,
  context,
  fallback = <span class="hljs-literal">null</span>,
  children,
}</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> hasPermission = usePermission(I, a, context);

  <span class="hljs-comment">// If `children` is a function, call it with `hasPermission`</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> children === <span class="hljs-string">'function'</span>) {
    <span class="hljs-keyword">return</span> &lt;&gt;{children(hasPermission)}&lt;/&gt;;
  }

  <span class="hljs-comment">// Otherwise, render children or fallback</span>
  <span class="hljs-keyword">if</span> (hasPermission) {
    <span class="hljs-keyword">return</span> &lt;&gt;{children}&lt;/&gt;;
  }

  <span class="hljs-keyword">return</span> &lt;&gt;{fallback}&lt;/&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Can;
</code></pre>
<h3 id="heading-policy-enforcement">Policy Enforcement</h3>
<p>You can use the <code>usePermission</code> hook for programmatic checks and the <code>Can</code> component for conditional rendering.</p>
<p><strong>1. Using</strong> <code>Can</code> <strong>to hide/show components</strong></p>
<pre><code class="lang-typescript">&lt;Can
  I=<span class="hljs-string">"approve"</span>
  a={post}
  context={system}
  fallback={&lt;p&gt;You <span class="hljs-keyword">do</span> not have access to <span class="hljs-keyword">delete</span> a comment.&lt;/p&gt;}
&gt;
  &lt;YourComponent /&gt;
&lt;/Can&gt;
</code></pre>
<p><strong>2. Using</strong> <code>Can</code> <strong>to disable components</strong></p>
<pre><code class="lang-typescript">&lt;Can
  I=<span class="hljs-string">"delete"</span>
  a={comment}
  context={system}
&gt;
  {<span class="hljs-function">(<span class="hljs-params">allowed</span>) =&gt;</span> (
     &lt;button onClick={deleteComment} disabled={!allowed}&gt;
       Delete Comment
     &lt;/button&gt;
   )}
&lt;/Can&gt;
</code></pre>
<p><strong>3. Using</strong> <code>usePermission</code> <strong>to create protected routes</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ProtectedRoute.tsx</span>

<span class="hljs-keyword">import</span> { Navigate, Outlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProtectedRoute</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> hasPermission = usePermission(<span class="hljs-string">"view"</span>, user, context);

  <span class="hljs-keyword">return</span> hasPermission ? &lt;Outlet /&gt; : &lt;Navigate to=<span class="hljs-string">'/login'</span> /&gt;
}

<span class="hljs-comment">// Route set-up</span>
&lt;Route element={&lt;ProtectedRoute /&gt;}&gt;
  &lt;Route path=<span class="hljs-string">'/'</span> element={&lt;Admin /&gt;} /&gt;
&lt;/Route&gt;
</code></pre>
<p><strong>4. Using</strong> <code>usePermission</code> <strong>to skip API calls</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> hasPermission = usePermission(<span class="hljs-string">"view"</span>, user, context);

<span class="hljs-keyword">const</span> { data: user, isLoading: isUserLoading, isError: isUserError } = useUserQuery({
    skip: !hasPermission,
});
</code></pre>
<p>That's it! Now, let's wrap up with a quick summary.</p>
<h2 id="heading-lets-summarize">Let’s Summarize</h2>
<p>In this handbook, you learned how to implement scalable access control using both CASL and a custom solution. We started by diving into different access control models, focusing on ABAC, and explored two ways to enforce ABAC-based rules.</p>
<p>With CASL, you saw how easy it is to define user abilities, whether you’re using a shared library or external permissions. We walked through how to set up access control for various user actions, all with clean, readable code. You also learned how to add advanced features like dynamic conditions and field-level access for even more granular control.</p>
<p>On the other hand, you also learned how to build a custom permission framework tailored to your app’s specific needs. You combined contextual state-based checks with policy-based rules, creating a flexible and scalable access control system. Along the way, you explored concepts like Policy as Code, CEL (Common Expression Language), custom hooks, caching, and conditional fetching using RTK queries. You also saw how to enforce access control on components, protected routes, and more.</p>
<p>Both approaches share some key benefits:</p>
<ul>
<li><p><strong>Dynamic and scalable</strong>: Adding new actions or entities is as simple as updating a single file – no code rewrites required.</p>
</li>
<li><p><strong>Separation of concerns</strong>: Keeps validation logic separate from UI components, which makes your code easier to maintain.</p>
</li>
<li><p><strong>Readable</strong>: You can define permissions using simple, conversational language like "<em>Can I read this post?</em>" or "<em>Can I create a comment?</em>"</p>
</li>
<li><p><strong>Reusable components</strong>: You can reuse wrapper components and hooks across your app to reduce duplication.</p>
</li>
<li><p><strong>State reactivity</strong>: Works seamlessly with React state, ensuring that your access control rules are reflected dynamically in your UI.</p>
</li>
</ul>
<h3 id="heading-further-scaling-considerations"><strong>Further Scaling Considerations</strong></h3>
<p>If your policy payload is cumbersome or validation logic is computationally expensive, consider the following optimizations:</p>
<ul>
<li><p><strong>Memoize the output</strong>: Use <code>useMemo</code> to cache the result of expensive computations, but be mindful that <code>useMemo</code> itself can be costly if overused.</p>
</li>
<li><p><strong>Modularize policies</strong>: Break down your policies into separate files based on their domain. Fetch only the essential policies at startup and lazy load non-essential ones on demand.</p>
</li>
<li><p><strong>Offload validation to the backend</strong>: Move policy validation logic to the backend and consider server-side rendering. But, keep in mind that some dynamic checks still need to occur on the frontend.</p>
</li>
</ul>
<p>Don’t forget to implement access control on the back-end too and make sure to filter-out sensitive data before sending it to the client!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Whether you choose CASL for its simplicity and power or implement your own custom solution for more flexibility, you now have the tools and knowledge to integrate access control into your React applications, ensuring your users can only access what they’re authorized to.</p>
<p>If you enjoyed reading this (or even if you didn’t ;)), drop me a message on <a target="_blank" href="https://www.linkedin.com/in/samhitharamaprasad/">LinkedIn</a> with your feedback.</p>
<p>Happy coding, and may your app's permissions be as scalable as your user base!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
