Chapter IV: Attribute-Based Access Control (ABAC)

Attribute-based authorization, or attribute-based access control (ABAC), is an access control framework oriented around attributes assigned to a user (e.g., a human, endpoint, or AI agent), a resource (e.g., database record or a file) or the environment (e.g., the time or IP address). For example, under ABAC, a user’s Location: NYC attribute might grant them access to the endpoints of an American data center. ABAC is a common authorization strategy that provides more flexibility than rigid models like role-based access control (RBAC) or access control lists (ACLs).

When should you use attribute-based authorization? ABAC is ideal whenever some data, which might not always fit the mold of roles or relationships, has an impact on authorization. That same data might also serve other purposes outside of authorization. By design, ABAC is very broad and could be tailored to an application’s specific access needs. However, in practice, ABAC is predominantly chosen due to one of two scenarios:

  1. A company has complex access requirements that would create a role explosion, where hundreds or potentially thousands of custom roles are needed to account for all the permutations (e.g., geographies, hierarchy, customer contracts, etc). These same requirements, however, could be reduced to a simpler set of logic around existing attributes.
  2. A company aspires to closely follow the principle of least privilege, where access incorporates parameters such as IP address, time of day, or device.

Conversely, for organizations that can group access into a few buckets, role-based access control might be preferred. Or, for organizations that could reduce access to relationships between resources and users, then relationship-based access control (ReBAC) is best.

What Part of the System is Attribute-Based Authorization?

In the previous chapter, we implemented RBAC as the authorization strategy for GitClub, our sample web application. Remember, authorization can be thought of as two parts: the decision and the enforcement. Once again, we are focused on the decision component, which includes both logic and data, when discussing ABAC.

Similar to our discussion of RBAC, we’ll cover different variants of ABAC commonly used by web applications. For each model, we’ll talk through:

  • What the authorization model is. Choosing the right authorization model depends on the desired user experience. We’ll talk about what the model represents from a high level, show examples of it in the real world, show when it makes sense to use, and describe what makes it a good fit for that use case.
  • How to implement the model. As we demonstrated in the RBAC chapter, implementing in ABAC involves data and logic. Authorization logic is the abstract set of rules that decide who can do what. These rules are expressed over the authorization data.

And once again, we’ll describe how to structure the authorization data to back our logic by including diagrams for how this could be stored in a database.

As a refresher on our sample application: GitClub is a website for source code hosting, collaboration, and version control, similar to real-life applications GitLab and GitHub. In GitClub, an example of a resource is a repository. Users may or may not be able to read or make changes to a repository.

What is an Attribute?

Attributes are pieces of data that live somewhere. That’s a fairly open-ended definition. They might be fields attached to a user that’s stored in a PostgreSQL table. They might be fields attached to a resource, compressed in a JSON blob format. They might be runtime values, like the timestamp or device’s IP address.

For example, an ABAC rule could say something like “the user’s office locale must match the asset’s storage locale, and the time must be working hours.” These values—the locale and the time—are just arbitrary pieces of data.

However, these attributes typically fall under four categories:

  • Subject (user): The user’s department, employment_type, clearance, roles (yes, roles are relationships, and relationships are attributes!), etc.
  • Resource: What’s being accessed has its own attributes, such as owner_org_id, visibility, sensitivity, or org_id.
  • Environment/Context: request_time, ip_range, or runtime_env (prod/staging).
  • Relationship: Two subjects, two resources, or a subject and a resource might have an established relationship that is stored in a pivot table. While ReBAC strictly builds on relationships, ABAC could factor them in as one of many attributes.

Notably, the action is not an attribute. That’s instead the verb of each policy, which are boolean expressions over attributes, (e.g., Read access if user.department == "Security" AND repo.sensitivity <= user.clearance).

Technically, RBAC and ReBAC frameworks are types of ABAC frameworks that specify what attributes should be considered (and in RBAC’s case, prescribe the shape and values of those attributes). But what makes an attribute distinct from a role or a relationship? Is there a meaningful difference between .folder and .isPublic? Well typically, ABAC implementations involve attributes that weren’t created to designate access or hierarchies—instead, ABAC opens the gates to any data that may or may not be purpose-designed around authorization.

Is ABAC natively more fine-grained?

A common description of ABAC is that Attribute-Based Access Control is a more fine-grained approach to authorization compared to RBAC and ReBAC, where the latter are considered to be more coarse-grained. However, this is a slight misunderstanding. The terms just compare the level of specificity of one implementation to another. For example, a pure RBAC system (e.g. user.role == MarketingNY) could be more fine-grained than a very coarse ABAC system (e.g. isPublic == true).

However, ABAC does make fine-grained authorization easier. ABAC can take advantage of any data, while fine-grained implementations of RBAC and ReBAC require excessive role types or relationship logic to keep up with increasing specificity.

ABAC provides more flexibility

On a similar note, ABAC objectively does provide more flexibility. While RBAC and ReBAC prescribe the shape of authorization data, ABAC can use any data that’s available. Many attributes exist, and with ABAC, you can use those attributes.

Conversely, if you relied solely on roles, there could be a role explosion; for example, consider the previous example where access is determined by an actor and a resource sharing the same locale. With RBAC, there would need to be a role for each individual locale (e.g. “marketerChicago”, "marketerNYC", "marketerSF" etc.); with ABAC, a simple boolean comparison is all that’s necessary (e.g. actor.locale == resource.locale)

How to scope attributes

Technically, ABAC allows for any arbitrary definition of rules that map any attribute permutation to an access decision. For instance, a rule could hypothetically be defined as user.department == "Sales" AND user.country == "United States" AND resource.labels.contains(user.country) AND ip_range == 168.14.34.23 AND "12:00pm" < request_time < "12:56pm". This might work for select scenarios, but if every access rule involved this many attributes and they were different attributes each time, then developers would get headaches understanding what logic might conflict.

Instead, there are some common ways to scope ABAC rules around attributes so that they’re easily manageable. These strategies can be combined or altered; they serve as a basis for how to organize access logic, not a restriction of what logic is permissible.

Entitlement-Based ABAC

A common strategy for scoping ABAC logic is around entitlements. Entitlements allow access to be controlled by a user’s role and their organization’s subscription tier. For instance, some users might be able to access a premium analytics feature because their organization has an enterprise plan.

Notably, entitlement-based ABAC grants access on the combination of their subscription plan and their role. In many cases, only a few users might have access to premium features.

For instance:

def allow_access(self, action):
        """Attribute-based access control decision function"""
        # Entitlement-based ABAC example
        if action == "read_analytics" and self.resource.type == "repository":
            return (self.user.organization == self.resource.organization and
                    self.resource.organization.plan == "premium" and 
                    self.user.is_org_admin)

Resource-Scoped ABAC

Another strategy is to scope access around shared traits between a resource and a user. Imagine repositories in GitClub can have labels like "security", "frontend", "backend", "compliance", etc. Similarly, users have department or expertise attributes assigned to their profiles.

For instance:

  • A repository labeled "security" and "compliance" with sensitivity level 3
  • A user with department "security" and clearance level 4

We can shape our ABAC logic around these shared resource traits:

def allow_access(user, action, repository, context=None):    
    # Resource-Scoped ABAC
    if action == "read":
        # Allow reading if user department matches repository labels
        return repository.labels.contains(user.department)
    
    if action == "write":
        # Allow writing if department matches labels AND user has sufficient clearance
        return (repository.labels.contains(user.department) and
                user.clearance_level >= repository.sensitivity_level)

Time-Based Access

Distinct from context-aware ABAC, time-based access is where a subject has access to a resource with an expiration. Time-based access is ideal for resources that should never have long-lasting access—such as confidential financial documents that a user was invited to view and approve.

In practice, time-based access might look like the following:

def allow_access(user, action, repository, context=None):
    if action == "read" and hasattr(user, "invites") and repository in user.invites:
        return context.time < user.invites[repository].timestamp
    return False

Context-Aware ABAC (Environment & Risk)

An approach that especially illustrates the flexibility of ABAC is context-aware scoping. This approach is ideal for high-risk resources that require a strict interpretation of the principle of least privilege. For example, some GitClub organizations might be working with HIPAA-protected data or financial information, where access to builds needs to be cross-checked for time, network ID, device, or environment.

ABAC is an ideal strategy for these scenarios because environmental attributes are readily available to be factored into access rules. For instance, a rule to protect merges into the production codebase might look like:

def allow_access(user, action, repository, context=None):
    if action == "merge":
        return (repository.default_branch == True and
                context.device_trust == "trusted" and
                business_hours(context.time, user.country) and
                user.clearance >= repository.sensitivity)
    return False

def business_hours(time, country):
		# some code detailing time and country
    return True  

Context-aware permissions are particularly apt for regulation-heavy environments or industries where threat actors might be more prevalent, like financial services.

Quick Reference To Choosing An Authorization Model

Earlier, we showed four variants of role-based authorization, detailing different ways to organize rules around attributes.

  • Scoping attributes around organizations to determine ownership. This is ideal for applications where most resources are globally accessible to members of an organization, even in varying degrees.
  • Scoping attributes around resources to determine ownership. This is ideal for applications where users have a direct relationship with resources. However, this might also be a good case for relationship-based access, discussed in the next chapter.
  • Fine-grained authorization through runtime parameters to strongly follow the principle of least privilege. This can be combined with other attribute orientations to provide an additional layer of security.
  • RBAC via ABAC, which is ideal for organizations that prefer an RBAC-based scheme but need some additional flexibility.

The examples that we’ve described here will cover many situations you’ll see in production. However, we haven’t detailed every policy yet. Specifically, some scenarios are better to be defined as relationships than shared attributes. In the next chapter, we’ll introduce relationship-based authorization to handle these cases.

Next chapter:

Relationship-Based Access Control (ReBAC)
In this chapter we’ll look at relationship-based authorization models. We’ll show you how to implement data ownership, parent-child resources, groups, and hierarchies. We’ll also explain centralized authorization services like Google Zanzibar, and share the Golden Rule for building authorization services.

Previous chapter:

Role-Based Access Control (RBAC)
Introducing authorization models – ways to structure authorization code and guide implementation. These models start simply, but can grow with changing requirements. In this section we cover four models of role-based access control and show how to implement each one.

The best way to learn is to get your hands dirty.