Blog

Relationship-based vs policy-based authorization: what's the difference and how do they work together?

Authorization rules can be expressed as policies, relationships, or both. Read how each one works, their pros and cons, and find the best for your case.


Authorization ensures that only authorized users can access specific resources in a system. Over time, various approaches have emerged, each offering different ways to define and enforce access control. These approaches generally fall into three categories: policies-based, relationships-based, and a combination of both.

In this article, we explore how each one of these approaches works, their pros and cons, and how combining them can give you the best of both worlds.

Policy-based authorization

Policy-based authorization determines access privileges dynamically based on policies and rules. It takes multiple parameters in consideration when making authorization decisions:

  • User attributes, like role.
  • Resource attributes, like which file or database is to be accessed.
  • Action attributes, like read or write.
  • Context or environmental attributes, like time of day or physical location.

With policy-based authorization we can make our access control adaptable to changing conditions: I might be able to access certain sensitive reports from my office Monday to Friday, but I cannot access them from my home computer during the weekend.

Policy-based authorization is typically stateless. In stateless authorization, every access request is independently evaluated without relying on any prior context or session information. Each request is treated as a self-contained decision, meaning that the system does not maintain state between requests. The authorization engine processes the incoming request along with the relevant data—such as roles, permissions, and attributes—without needing to reference any session or historical data.

Two popular frameworks that use policies are Opa and Cedar.

Opa

Open Policy Agent (OPA) is one of the most popular policy engines used to manage authorization. It allows organizations to define policies using a high-level declarative language called Rego, which is easy to understand and flexible enough to express complex rules. These policies are generally applied to a wide range of resources, from microservices to Kubernetes clusters.

This is an example using Rego that allows managers to access the financial_report resource only during business hours (from 9:00 AM to 5:00 PM).

	
package authz

default allow = false

allow {
    input.user.role == "manager"
    input.resource == "financial_report"
    input.timestamp >= 9:00
    input.timestamp <= 17:00
}
	

The main advantage of policy-based approaches like OPA is their ability to offer fine-grained control over access decisions. However, such systems can become cumbersome as the number of policies grows, especially in larger organizations with complex access control needs.

OPA is typically stateless in terms of policy evaluation. It makes decisions based on the input data provided at runtime (such as attributes of the request and resource) and the policy rules defined. OPA itself does not inherently store state or maintain context across requests. However, it can be used with external systems that provide state or store persistent information (e.g., databases or caching layers) for more complex scenarios.

Cedar

Cedar (Capability-based Enforcement of Authorization Rules) is another policy language designed to specify and enforce authorization decisions. Cedar allows for the creation of policies that define what actions are allowed under various conditions, based on entities, roles, and attributes.

This is an example that allows a user to access a document only if they have a specific capability that allows them to view that document.

	
allow {
    userHasCapability(user, "view", "document", resource_id)
    resourceExists(resource_id)
}

userHasCapability(user, action, resource_type, resource_id) :-
    capability(user, action, resource_type, resource_id).
    
capability(user, action, resource_type, resource_id) :-
    // Define specific capabilities that users have.
    // For example, the user has the "view" capability on documents with resource_id "12345".
    capability_for_user(user, "view", "document", "12345").

capability_for_user(user, action, resource_type, resource_id) :-
    // This rule represents the capabilities that a user might have.
    // Example: User "alice" can view document "12345".
    user == "alice" & action == "view" & resource_type == "document" & resource_id == "12345".
    
resourceExists(resource_id) :-
    // Check if the resource exists.
    resource_id == "12345".
	

In a real-world application, this type of policy can be dynamically generated based on a user's roles, groups, or relationships to the resources. For example, instead of hardcoding alice and 12345, the system could evaluate attributes or metadata about the user and resource to determine what capabilities to grant.

Like OPA, Cedar's strength lies in its centralization of authorization logic and the ability to define fine-grained access controls. The key distinction between Cedar and OPA is that Cedar primarily focuses on defining access control rules via capabilities (specific grants of authority to perform actions), whereas OPA is more versatile and can be applied to a broader set of use cases beyond authorization, such as configuration management and policy enforcement.

Cedar is typically stateless. Its policies are based on conditions evaluated at runtime, meaning each access request is evaluated independently, with no persistent state maintained between requests. Cedar does not store or track user relationships or roles on its own; it depends on external systems to manage that state (e.g., identity providers or access management systems).

Pros and cons

Some of the pros of policies-based access control are:

  • Policies-based authorization allows for centralized policy definition and management. You can define all access control rules in a single policy repository, making it easier to update and audit access controls across an entire organization or system.
  • Policies can be very granular, allowing for sophisticated access rules based on a variety of attributes (e.g., roles, attributes, relationships, actions). For example, you can enforce policies that depend on a user’s role, location, time of day, specific resource, or even specific actions like "view" vs. "edit."
  • Policies are typically defined using declarative languages (e.g., Rego in OPA, Cedar). This makes the system highly flexible and extensible. You can define complex logic for access decisions and adjust the policies as the system evolves or as new requirements arise. Policy based systems are dynamic by nature, making them adaptable to an organization's needs.

Some of the cons of policies-based access control are:

  • Evaluating policies at runtime, especially in large systems with many rules and data inputs, can add performance overhead. The policy engine must process each request and evaluate it against potentially complex policies, which may slow down response times in high-traffic applications. Data is not usually stored in a performant manner which can slow down things further.
  • Writing and managing policies in specialized languages (like Rego or Cedar) can have a steep learning curve, especially for teams that are not familiar with the concepts of declarative policy definition or formal access control mechanisms.
  • In a microservices architecture, policies are often distributed across multiple services, which can make auditing access control decisions more difficult. Each service might enforce its own set of policies locally, and these policies could be written in different policy languages (e.g., Rego for OPA, Cedar, etc.). Because policies are enforced by individual services, they may not be centrally logged or aggregated, making it harder to have a single view of all access control decisions across the entire system. Access logs may be dispersed across different services, and combining them into a comprehensive audit trail might require additional integration or tooling. This can make auditing the policies themselves, and the access decisions they generate, more complex and time-consuming.

Relationships-based authorization

Whereas policy-based systems typically focus on static rules and conditions, relationship-based systems focus on the relationships between users, resources, and other entities. These systems often take into account dynamic relationships that can be used to define access control.

Relationships or data-based systems are typically stateful. This means that the system remembers and stores context (such as session data, user interactions, or history of access decisions) across multiple requests. An authorization decision might depend on the context of a user’s previous actions, roles, or states that were recorded during their session.

Two popular models that use relationships are ReBAC and Zanzibar.

ReBAC (Relationship-Based Access Control)

Relationship-based access control (ReBAC) is an access control model that defines authorization rules based on the relationships between users and other entities within a system. In ReBAC, the access decision is not solely based on roles or permissions but on the nature of the relationship between the user and the resource.

This is often used in social media platforms, collaboration tools, or enterprise systems, where the relationship between a user and a resource (such as being a friend, colleague, or team member) is key to determining access. In a social media platform, a user may have access to another user's posts only if they are friends, follow each other, or have some other type of relationship. ReBAC models allow for more nuanced and context-sensitive access decisions that account for complex relationships such as “manager of,” “owner of,” or “team member in.”

Let’s say, for example, that we want to implement access control for a collaborative document sharing platform, and we want to enforce these rules:

  • A user can edit a document if they are an editor of that document.
  • A user can view a document if they are either the owner, an editor, or have been explicitly granted view permission.
  • A user can share a document if they are the owner or have the appropriate share permission.

This could look like that using ReBAC:

	
{
  "relationships": {
    "Alice": {
      "Document 1": "owner",
      "Document 2": "editor"
    },
    "Bob": {
      "Document 1": "editor",
      "Document 2": "viewer"
    },
    "Charlie": {
      "Document 1": "viewer",
      "Document 2": "editor"
    }
  },
  "permissions": {
    "view": [
      "owner",
      "editor",
      "viewer"
    ],
    "edit": [
      "owner",
      "editor"
    ],
    "share": [
      "owner"
    ]
  }
}
	

This model defines a set of permissions, and a set of relationships:

  • Relationships:
    • Alice is the owner of Document 1 and an editor of Document 2.
    • Bob is an editor of Document 1 and a viewer of Document 2.
    • Charlie is a viewer of Document 1 and an editor of Document 2.
  • Permissions:
    • To view a document, a user must be either the owner, an editor, or have viewer permission.
    • To edit a document, the user must be either the owner or an editor.
    • Only the owner can share a document.

We can apply these rules to determine if a user can perform certain actions. For example, we can see that Alice has the right to view Document 1 because she is the owner of that file and according to the permissions rules, owners can view documents.

The strength of ReBAC lies in its ability to model intricate relationship networks, making it well-suited for collaborative environments or systems with highly dynamic user interactions.

ReBAC is generally stateful because it relies on dynamic relationships between users and resources. In ReBAC, access control decisions are made based on the relationships a user has with a particular resource (e.g., “Alice is the owner of Document 1”). These relationships need to be stored and maintained to evaluate access permissions correctly. Therefore, the system needs to retain state to manage these relationships and grant access accordingly.

Zanzibar

Zanzibar, developed by Google, is a highly scalable system designed to manage authorization and access control in distributed systems. Zanzibar is an implementation of ReBAC, focusing on providing fine-grained, efficient access control for large-scale systems like Google’s own services.

Zanzibar uses a tuple-based approach to represent relationships between users and resources, where access is determined by both the relationships and attributes of users and resources. Zanzibar focuses on simplifying the authorization process in complex systems, allowing organizations to manage large-scale access control decisions without performance degradation.

Zanzibar's architecture and model are highly adaptable, allowing organizations to handle different access patterns, such as user-to-user relationships (as in ReBAC), group-based permissions, and even role-based access control (RBAC) combined with dynamic data relationships.

Let’s see how the collaborative document sharing platform example we saw earlier with ReBAC, looks like with Zanzibar:

	
{
  "users": {
    "alice": {
      "relationships": {
        "document1": ["owner"],
        "document2": ["editor"]
      }
    },
    "bob": {
      "relationships": {
        "document1": ["editor"],
        "document2": ["viewer"]
      }
    },
    "charlie": {
      "relationships": {
        "document1": ["viewer"],
        "document2": ["editor"]
      }
    }
  },
  "documents": {
    "document1": {
      "permissions": ["view", "edit", "delete"]
    },
    "document2": {
      "permissions": ["view", "edit"]
    }
  },
  "access_policies": {
    "view": {
      "allowed_roles": ["owner", "editor", "viewer"]
    },
    "edit": {
      "allowed_roles": ["owner", "editor"]
    },
    "delete": {
      "allowed_roles": ["owner"]
    }
  }
}
	

Zanzibar uses a relationship graph to represent relationships and permissions between users and resources (e.g., documents). It defines which users have which roles and what actions they can perform.

For example, user Alice has the right to delete document1 because she is the owner of the file, and according to the access policies the delete permission is only granted to owners.

Zanzibar is a stateful system. It tracks relationships between users, resources, and roles over time. These relationships, along with access control policies, are stored persistently. When a user requests access to a resource, Zanzibar checks the current state of the user's relationship with that resource (e.g., whether they are an owner, editor, or viewer). Zanzibar needs to maintain state to evaluate access control decisions based on these evolving relationships.

Pros and cons

Some of the pros of relationships-based access control are:

  • Relationship-based authorization is hyper performant because data is stored in an easy to query manner (e.g. Zanzibar). Most application data is hierarchical (roles, custom roles, folder-structures) and easy to represent and group.
  • Easy to audit in real time because the data is centralized.
  • Systems like Zanzibar can scale to billions of users and resources by distributing the relationship and access control data across multiple services. The system can efficiently manage and enforce fine-grained access control at a global scale.

Some of the cons of relationships-based access control are:

  • Zanzibar's centralized nature requires constant synchronization between your application and the authorization system, which can introduce complexity and latency, especially in rapidly changing environments.
  • Relationship-based systems can face scalability challenges as the number of users, resources, and relationships increases. In large-scale environments, managing and querying vast numbers of relationships can result in performance bottlenecks, particularly if real-time access control decisions are required. Scaling it is no small feat but it can be done.

Combining policies and relationships

In practice, most modern systems combine both policy-based and data/relationship-based approaches to achieve a more flexible and dynamic authorization system. The combination of these approaches can address a wider range of use cases and scale better across diverse environments.

Organizations often combine policy-based frameworks like OPA or Cedar with relationship-based models like ReBAC or Zanzibar to address both static and dynamic access control needs.

For instance, consider a company using both OPA and ReBAC:

  • OPA might be used to define broad, static rules such as “Only administrators can configure the system” or “Users with a certain role can access certain APIs.”
  • ReBAC or Zanzibar can then be applied to more granular, dynamic access control based on relationships, such as “A user can access a project if they are a member of that project” or “Only users who are directly connected in the social network can view each other's profiles.”

The combination of these models allows for a highly flexible authorization system that can scale to meet both well-defined security policies and dynamic, context-sensitive access rules.

Combining policies with relationships has practical benefits:

  • Optimized performance: Stateful systems (like ReBAC and Zanzibar) are beneficial for tracking ongoing relationships and maintaining context, but they can become a bottleneck when scaling large systems due to their persistent state storage and the need to manage large amounts of data. Stateless systems, on the other hand, are highly scalable and efficient for evaluating simple access control rules without having to maintain context between requests. By using stateless authorization for routine access decisions that don't require deep context (e.g., simple resource access or quick role checks) and stateful authorization for scenarios that need to track complex relationships or user contexts (e.g., managing long-lived user sessions or permissions that evolve over time), you can combine performance with scalability. This ensures that the system can handle high traffic while still maintaining fine-grained control when necessary.
  • Scalability: By separating static policy enforcement from dynamic data-driven access control, systems can scale more efficiently, handling large numbers of users, resources, and relationships.
  • Flexibility: Combining policies and data/relationships enables fine-grained access control in situations where roles alone might not be sufficient.
  • Security: Policy-based systems ensure that basic security principles are enforced (e.g., ensuring that only authorized roles can access certain services), while relationship-based systems allow for richer and more context-sensitive access decisions.
  • Simplified development: Some system components (like API services) might not need to maintain context about user sessions or state, while others (like collaboration tools) may involve user relationships, resource ownership, or team-based permissions. By using both policies and relationships you can compartmentalize and specialize different components of the system and end up with cleaner, more maintainable codebase.

There is no single winner in the policies vs relationships debate. A good centralized authorization system should support both.

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.