Resources

Authorization Resource Center

Master authorization fundamentals with our guides, events, and more.
ABAC, RBAC, ReBAC

TLDR;

  • RBAC (Role-Based Access Control) shifts the responsibility of access management from individual users to reusable roles. Instead of attaching dozens of permissions directly to each user, you assign them to a role like admin, contributor, or viewer, each with its own set of permissions, which keeps access predictable and consistent as systems grow.

  • RBAC has use cases across industries like healthcare, banking, cloud, SaaS, and Kubernetes, helping secure sensitive data through least-privilege access control. Doctors can update patient records, auditors in banks have read-only access, and developers in cloud setups are limited to scoped services—each role enforces clear boundaries.

  • RBAC not only secures sensitive data but also prevents cross-tenant access in SaaS platforms and reduces risks of misconfiguration in large-scale infrastructure. By structuring roles carefully, organizations maintain predictability and avoid accidental privilege escalation.

  • The mechanics of RBAC vary by system but follow the same principle: permissions are mapped to roles, then enforced via queries, IAM policies, or Kubernetes bindings. Whether database-level row filtering or JSON policy in AWS, the goal is consistent authorization.

  • Authorization providers like Oso make real-world RBAC setups easier by providing declarative role definitions, drag-and-drop assignments, and built-in testing. This removes the need for hand-coded policies in languages like Rego (policy language used by the Open Policy Agent (OPA) to define and enforce authorization rules) and ensures authorization stays secure, scalable, and maintainable.

RBAC, is one of the most widely used authorization models in software systems like finance, HR, CRMs, etc. Instead of attaching permissions directly to users, RBAC assigns permissions to roles, and users assume those roles. This indirection simplifies access management and keeps authorization consistent as applications scale.

Common role examples include:

  • Project Manager: Create tasks, assign work, and close projects
  • Contributor: Create and update tasks
  • Viewer: Read project status but cannot edit

For example, in a project management tool like Jira or Asana, you might define a project_manager role with permissions to create tasks, assign work, and close projects. Team members take the contributor role, which only allows creating and updating tasks. A client user might be assigned a viewer role, restricted to reading project status but not editing it. By modeling access this way, you don’t manage permissions for every individual user; you manage roles and apply them across the system.

The importance of RBAC becomes clear in multi-tenant and collaborative systems like project management tools, customer support platforms, or SaaS CRMs. In a customer support application, one user could be a support_manager with permissions to assign tickets and update workflows, while another is a support_agent limited to responding to tickets. An account_admin might configure organization-wide settings, and a billing_viewer could access invoices without the ability to change subscription details.

The problem is that RBAC often becomes hard to maintain over time. Teams usually begin with two roles, admin and user, but soon add exceptions like read_only_admin or limited_user, leading to what is known as role explosion. Permissions end up scattered across the codebase, and developers can no longer confidently answer, Who has access to what?

In this post, we will examine RBAC examples, hierarchical roles, resource-scoped roles, action-constrained roles, and hybrid models that address these challenges and keep role systems manageable.

Top 5 RBAC Examples In the Real World

RBAC (Role-Based Access Control) is best understood not through theory but by looking at how it works in real-world scenarios. Instead of abstract policies or technical rules, it is the practical use cases that show how roles and permissions are assigned to manage access effectively.

Before moving forward, let’s understand two important terms often used in RBAC implementations: Polar and Rego.

Polar is the declarative policy language used by Oso, an open-source authorization framework. It allows developers to define access control logic in a structured and readable way. With Polar, you can express who can perform what action on which resource using simple rules. It integrates directly into application code, making authorization decisions consistent and easy to maintain.

Rego, on the other hand, is the policy language used by Open Policy Agent (OPA). It’s a more general-purpose language that can enforce policies across systems, like APIs, CI/CD pipelines, Kubernetes clusters, or microservices. Rego policies define rules for evaluating requests and returning authorization decisions, making it suitable for large-scale, distributed environments.

Looking at such examples helps clarify how permissions, roles, and responsibilities translate into practice. So let’s start one-by-one:

RBAC in Healthcare Systems

Healthcare applications need a clear separation of responsibilities between medical, financial, and administrative staff. Doctors update patient records, nurses maintain vitals and medication details, receptionists handle appointments, and administrators manage system settings.

In the healthcare sector, organizations like Tamr have used Oso's authorization platform to secure regulated, high-sensitivity data in the cloud. By implementing fine-grained access controls, Tamr enables users to access only the data pertinent to their roles, thereby reducing the risk of unauthorized access and ensuring compliance with healthcare regulations.

Similarly, Kaleidoscope, a biotech R&D platform, utilizes Oso to manage access to sensitive research data. Their platform allows lab and computational teams to plan and track scientific work, integrating data and streamlining collaboration, all while maintaining strict access controls to protect intellectual property and comply with industry standards. 

These examples demonstrate how RBAC, when implemented effectively, can protect sensitive healthcare data, ensure compliance with regulations, and streamline workflows by providing appropriate access to users based on their roles.

Role Permissions
Doctor View/update patient records, prescribe meds
Nurse Update vitals, administer treatments
Receptionist Schedule appointments, manage patient intake
Administrator Manage users, audit logs, system settings

A common implementation is role–permission mapping in the database. When a user calls an API endpoint, the system validates whether the role tied to that user includes the requested permission. 

With Oso Polar policies, we can define these roles and their permissions directly in code. Instead of relying on SQL queries to validate access, the policy itself enforces which actions each role can perform.

actor User {}

resource HealthcareSystem {
  roles = ["doctor", "nurse", "receptionist", "administrator"];
  permissions = [
    "view_patient_record", "update_patient_record", "prescribe_medication",
    "update_vitals", "administer_treatment",
    "schedule_appointment", "manage_patient_intake",
    "manage_users", "audit_logs", "system_settings"
  ];

  # doctors
  "view_patient_record" if "doctor";
  "update_patient_record" if "doctor";
  "prescribe_medication" if "doctor";

  # nurses
  "update_vitals" if "nurse";
  "administer_treatment" if "nurse";

  # receptionists
  "schedule_appointment" if "receptionist";
  "manage_patient_intake" if "receptionist";

  # admins
  "manage_users" if "administrator";
  "audit_logs" if "administrator";
  "system_settings" if "administrator";
}

When an API call is made, Oso evaluates whether the requesting user has the right role to perform that action. For instance, a receptionist cannot update patient records because the "update_patient_record" permission is never linked to their role. This ensures least-privilege access without manually writing SQL checks.

RBAC in Banking Applications

Banking applications require strict separation of duties to prevent fraud and ensure compliance with financial regulations. Customers, tellers, managers, and auditors operate in the same system but must have carefully bounded access. 

For example, customers should never query or update another user’s account, while tellers can process transactions but not authorize unusually large ones. Managers handle escalations and approvals, and auditors must review immutable records without the ability to alter them.

Here’s an example of what roles and permissions might look like in a typical banking application. This table illustrates how different roles are defined to control access to sensitive financial operations and ensure secure handling of customer data:

Role Permissions
Customer View balance, transfer funds, download statements
Teller Process deposits/withdrawals, verify customer identity
Manager Approve large transactions, manage teller activities
Auditor Read-only access to all financial logs

We’ll implement this RBAC in Polar using Oso to define these roles and their permissions directly in code instead of relying on row-level database security.  Consider this policy:

actor User {}

resource BankSystem {
  roles = ["customer", "teller", "manager", "auditor"];
  permissions = [
    "view_balance", "transfer_funds", "download_statements",
    "process_transaction", "verify_identity",
    "approve_large_transaction", "manage_tellers",
    "view_audit_logs"
  ];

  # customers
  "view_balance" if "customer";
  "transfer_funds" if "customer";
  "download_statements" if "customer";

  # tellers
  "process_transaction" if "teller";
  "verify_identity" if "teller";

  # managers
  "approve_large_transaction" if "manager";
  "manage_tellers" if "manager";

  # auditors
  "view_audit_logs" if "auditor";
}

In this setup, customers can view their balance, transfer funds, and download account statements within their own accounts. Tellers handle deposits, withdrawals, and customer verification but cannot approve high-value transactions. Managers have the authority to approve those transactions and supervise teller activities, adding a layer of oversight. Auditors maintain read-only access to financial logs for compliance checks without the ability to modify records.

Instead of scattering access rules across SQL queries, Oso centralizes these permissions in the policy. For example, when a teller attempts to approve a large transaction, Oso will deny the request because "approve_large_transaction" is only mapped to managers. This makes access decisions clear, auditable, and consistent across the entire banking system.

RBAC in Cloud Infrastructure (AWS IAM)

Cloud environments demand strict access segmentation because misconfigured permissions are one of the most common causes of breaches. In AWS, Identity and Access Management (IAM) enforces access through roles and policies. 

Role Permissions
Developer Manage Lambda, CloudWatch logs; no IAM modifications
DevOps Engineer Provision/manage EC2, ECS, EKS, CloudFormation
Auditor Read-only access across all AWS resources
Administrator Full access to IAM, billing, and all AWS services

Similarly, with Oso Polar, we can represent cloud personas such as developers, DevOps engineers, auditors, and administrators with scoped permissions that mirror least-privilege IAM setups.


actor User {}

resource CloudAccount {
  roles = ["developer", "devops", "auditor", "administrator"];
  permissions = ["read", "write", "manage_lambda", "view_logs", "manage_ec2", "manage_ecs", "manage_eks", "manage_cloudformation", "manage_iam", "manage_billing", "full_access"];

  # role-permission mapping
  "manage_lambda" if "developer";
  "view_logs" if "developer";
  "manage_ec2" if "devops";
  "manage_ecs" if "devops";
  "manage_eks" if "devops";
  "manage_cloudformation" if "devops";
  "read" if "auditor";  # auditors get read permission
  "manage_iam" if "administrator";
  "manage_billing" if "administrator";
  "full_access" if "administrator";
}

Developers can deploy and debug workloads by managing Lambda functions and viewing logs but have no access to IAM or billing. DevOps engineers manage infrastructure components such as EC2, ECS, EKS, and CloudFormation stacks without account-level governance rights. Auditors operate with read-only access to review system activity, while administrators retain full privileges, including IAM and billing, forming the highest trust boundary.

So, by centralizing access rules in Oso, permissions become explicit, consistent, and auditable across the entire cloud environment. For example, a developer attempting to modify IAM policies will be denied because "manage_iam" is tied exclusively to the administrator role. This prevents privilege creep and enforces least-privilege access in the same spirit as AWS IAM’s JSON policies, but expressed at the application layer for clarity and maintainability.

RBAC in Multi-Tenant SaaS Platforms

In SaaS environments, RBAC must not only manage user actions but also enforce strict tenant isolation. A contributor from one company should never gain access to another company’s data, even if their role name matches. Roles such as tenant admin, team lead, contributor, and viewer must always be scoped to the tenant ID at both the application and authorization layers.

With Oso Polar policies, we can model this tenant-aware RBAC directly. Each role is defined relative to a tenant resource, ensuring that permissions cannot leak across organizational boundaries.

Role Permissions
Tenant Admin Manage users, roles, billing, and settings for their own tenant
Team Lead Create and manage projects, assign contributors within their tenant
Contributor Update project tasks, add comments, upload files within assigned projects
Viewer Read-only access to projects and tasks; cannot modify any data

Have a look at the example policy of a Multitenant SaaS environment in Polar: 

actor User {}

resource Tenant {
  roles = ["tenant_admin", "team_lead", "contributor", "viewer"];
  permissions = [
    "manage_users", "manage_roles", "manage_billing", "manage_settings",
    "create_project", "manage_project", "assign_contributors",
    "update_task", "add_comment", "upload_file",
    "view_project", "view_task"
  ];

  # tenant admin
  "manage_users" if "tenant_admin";
  "manage_roles" if "tenant_admin";
  "manage_billing" if "tenant_admin";
  "manage_settings" if "tenant_admin";

  # team lead
  "create_project" if "team_lead";
  "manage_project" if "team_lead";
  "assign_contributors" if "team_lead";

  # contributor
  "update_task" if "contributor";
  "add_comment" if "contributor";
  "upload_file" if "contributor";

  # viewer
  "view_project" if "viewer";
  "view_task" if "viewer";
}

Tenant scoping is enforced at runtime by combining user identity and tenant claims, often carried in JWT tokens. Middleware verifies the token, extracts the tenant ID, and ensures that every Oso allow check is executed in the context of the correct tenant resource. 

For example, a contributor from tenant=acme_corp can update tasks in their own projects, but the same role tied to another tenant, such as tenant=globex_inc, has no access to Acme’s data. This guarantees tenant isolation while still supporting flexible, role-based permissions inside each organization.

RBAC in Kubernetes Cluster Management

Kubernetes includes Role-Based Access Control (RBAC) as a first-class feature of the API server, allowing administrators to define which users or service accounts can perform specific actions on resources. This prevents accidental disruptions, such as a developer deleting workloads or modifying cluster-wide configurations. 

Common roles include the cluster admin, who has unrestricted access across all namespaces and resources; the namespace admin, who manages deployments, services, and configurations within a single namespace; the developer, who is restricted to basic workload creation in their assigned namespace; and the auditor, who is limited to read-only visibility of resources, logs, and events.

Role Permissions
Cluster Admin Full access across all namespaces, nodes, policies, and API resources
Namespace Admin Manage workloads, deployments, services, and config within a namespace
Developer Create and view pods, deployments, or config in their assigned namespace
Auditor Read-only access to cluster resources, logs, and events

This RBAC structure can be expressed in Oso Polar, modeling clusters and namespaces as resources, with roles scoped to each resource. Cluster-level roles like cluster_admin and auditor provide broad privileges, while namespace-specific roles like namespace_admin and developer allow finer-grained access. 

Roles can also inherit permissions from higher-level resources, ensuring that a cluster admin automatically gains management permissions in all namespaces. This approach mirrors Kubernetes’ native RBAC system while using Oso’s declarative, testable authorization model.

actor User {}

resource Cluster {
  roles = ["cluster_admin", "auditor"];
  permissions = ["full_access", "read_only"];

  "full_access" if "cluster_admin";
  "read_only" if "auditor";
}

resource Namespace {
  roles = ["namespace_admin", "developer", "auditor"];
  permissions = ["manage_workloads", "create_pods", "view_resources", "list_pods"];

  relations = { cluster: Cluster };

  "manage_workloads" if "cluster_admin" on "cluster";
  "view_resources" if "auditor" on "cluster";

  "manage_workloads" if "namespace_admin";
  "create_pods" if "developer";
  "list_pods" if "developer";
  "view_resources" if "auditor";
"auditor" if "developer";
"developer" if "namespace_admin";
}

# Example Test: Simulate users performing actions
test "developer can create and list pods in assigned namespace" {
  setup {
    has_role(User{"maria"}, "developer", Namespace{"dev"});
    has_relation(Namespace{"dev"}, "cluster", Cluster{"prod"});
  }

  # Developer actions
  assert allow(User{"maria"}, "create_pods", Namespace{"dev"});
  assert allow(User{"maria"}, "list_pods", Namespace{"dev"});
  assert_not allow(User{"maria"}, "manage_workloads", Namespace{"dev"}); # Not allowed
}

test "auditor can view resources but not manage workloads" {
  setup {
    has_role(User{"stephan"}, "auditor", Namespace{"dev"});
    has_relation(Namespace{"dev"}, "cluster", Cluster{"prod"});
  }

  assert allow(User{"stephan"}, "view_resources", Namespace{"dev"});
  assert_not allow(User{"stephan"}, "create_pods", Namespace{"dev"});
  assert_not allow(User{"stephan"}, "manage_workloads", Namespace{"dev"});
}

In this Polar example, maria is a developer who can create and list pods in her assigned namespace but cannot manage workloads, while stephan is an auditor who can view resources but cannot modify or create workloads. Relations link namespaces to clusters, enabling inheritance of cluster-level permissions and ensuring consistent enforcement of least privilege. This model mirrors Kubernetes RBAC behavior in a declarative, testable, and maintainable way.

Challenges with RBAC in Rego

While Role-Based Access Control provides structure and scalability for authorization, implementing it in real-world applications is rarely straightforward. Even experienced developers encounter hurdles when defining, maintaining, and enforcing roles and permissions across complex systems.

Policy Complexity and Syntax Errors

Writing RBAC policies directly in Rego can be error-prone. Small mistakes in indentation, variable naming, or logical operators can result in policies that fail silently or incorrectly deny access. Developers often spend hours debugging why a seemingly correct rule does not grant or restrict access as intended. As the number of roles, resources, and conditional rules grows, the policy code becomes harder to read, test, and maintain.

Example:
A missing array index or a typo in a role name might allow a user to escalate privileges unintentionally. Debugging these subtle errors often requires inspecting the full evaluation trace of the Rego engine, which can be time-consuming.

# Intended policy: only 'admin' can delete repos
allow {
    input.action == "delete_repo"
    input.role == roles[_]   # roles = ["admin"]
}
# Bug: 'admins' typo instead of 'admin'
roles = ["admins"]

In this case, the policy silently fails because "admins" does not match the actual role "admin". Instead of denying access cleanly, the rule never evaluates as expected, which can create confusion during testing. 

Resource and Context Explosion

RBAC roles are static and don’t capture conditions like time, location, or environment. Without attributes layered on top, roles become either too permissive or too restrictive, making the access model misaligned with real-world requirements.

Example:
A developer might have different roles for dozens of repositories, each with slightly different permissions. Without careful abstraction, adding a new repository or changing a permission could require updating multiple Rego rules, introducing the risk of inconsistencies.

Debugging User Reports of Wrong Access

A customer reports that a "project_admin" cannot create new projects. The developer inspects logs but finds no error, Rego simply evaluates the policy as false. To diagnose, they must run opa eval with full input JSON and inspect the trace output. This requires reconstructing the exact request context (user role, resource, action, tenant ID, time-based constraints), which is error-prone and slow under production pressure.

Tools like Oso are designed to simplify these challenges by providing built-in support for roles, hierarchies, and relationships. Instead of hand-coding every policy in a textual language, Oso uses Polar and provides a drag-and-drop, input-driven approach to define roles, permissions, and resource constraints. 

Developers can configure roles and access rules using structured input values rather than manually writing full policy code. These definitions can then be applied directly in the application, reducing the risk of syntax errors and simplifying the process of scaling RBAC examples across multiple resources and environments.

Example: Implementing RBAC with Oso

Oso is an authorization framework designed to make access control easier to implement, maintain, and scale in applications today. Unlike hand-coded policies in Rego or custom checks scattered throughout application logic, Oso provides a centralized, declarative approach using its own policy language, Polar.

Oso allows you to define roles, permissions, and resource constraints in a way that is reusable, auditable, and directly enforceable in your app. Rather than manually writing long conditional statements, you describe what access looks like, and Oso evaluates these rules at runtime.

Let’s understand how to create an RBAC example in Oso:

Step 1: Define Roles and Resources

In the Rules Editor (Workbench mode), you start by selecting the type of resource (e.g., Item) and the actor type (User). Then, you add roles (like viewer or owner) and assign permissions to each role using a form-based interface with drag-and-drop menus.

This policy ensures that users with a given role automatically receive the correct permissions on the resource.

Step 2: Assign Roles to Users

After defining roles and permissions, you assign roles to individual users. Using a form-based interface with drag-and-drop menus, you add facts like:

has_role(User{"alice"}, "viewer", Item{"foo"});

has_role(User{"bob"}, "owner", Item{"foo"});

This visually maps users to roles on specific resources. Oso automatically integrates these assignments into the Polar policy.

Step 3: Make Authorization Decisions

Once roles and permissions are configured, you can check if a user can perform a specific action on a resource. For instance, check if alice has the view permission on Item: foo.

In the interface:

  • Select User: alice
  • Select Action: view
  • Select Item: foo
  • Click Check access

Oso evaluates the request against the policy and the assigned roles. The output will indicate whether the action is ALLOWED or DENIED, along with an explanation.

Step 4: Run Tests

The platform allows you to create and run tests to validate your RBAC setup. For example, you can assert:

assert allow(User{"alice"}, "view", Item{"foo"});

assert_not allow(User{"alice"}, "edit", Item{"foo"});

After running the test, Oso shows results directly in the interface, confirming that the roles and permissions are correctly enforced.

Let’s see the dashboard of Oso here:

In this screenshot, the Oso Cloud Rule Editor is being used to model and test Role-Based Access Control (RBAC). A resource called Item is defined with two roles, viewer and owner, and two permissions, view and edit. The policy specifies that a viewer can only view an item, while an owner can both edit and view it (since owners inherit viewer privileges). 

Users are then assigned roles on a specific resource: Alice is assigned the viewer role, and Bob is assigned the owner role on the same Item("example"). To validate these assignments, a test is created that checks different access scenarios. The assertions confirm that Alice can view but cannot edit, while Bob can both view and edit. 

Since all assertions pass, the test shows that the RBAC rules are correctly enforced, preventing unauthorized actions and allowing legitimate ones. Essentially, Oso here is being used to write authorization policies, link roles to permissions, assign users to those roles, and verify that the system enforces the intended access boundaries.

Conclusion

Role-Based Access Control is a reliable framework for managing permissions by assigning them to roles rather than individual users. This approach simplifies access management, ensures consistency, and scales well across complex systems. 

Real-world examples, from project management tools to code hosting platforms, show how users can hold different roles across resources, allowing precise control without administrative overhead.

RBAC can become complex as roles multiply, but hierarchical, resource-scoped, and hybrid RBAC-ABAC models help maintain clarity and security. Tools like Oso further simplify implementation by providing a centralized, declarative interface for defining roles, permissions, and constraints, along with built-in testing to verify access rules. Using structured RBAC models with such tools ensures predictable, secure, and maintainable access control in modern applications.

FAQs

What is Role-Based Access Control (RBAC) and why is it used?

RBAC (Role-Based Access Control) is an authorization model that assigns permissions to roles rather than individual users. Users assume these roles, which simplifies access management, ensures consistency, and reduces the risk of unauthorized access, especially in large or multi-tenant systems.

What is an example of an RBAC role?

A common RBAC role is Project Manager in a project management system. A Project Manager can create tasks, assign work, and close projects. Other roles might include Contributor (can create and update tasks) and Viewer (can only read project status).

What is the difference between an IAM role and RBAC?

IAM roles grant temporary, as-needed permissions in cloud environments, for example, a Lambda function assuming a role to perform a task. RBAC is a broader model where users are assigned roles with associated permissions, usually persistent, though ephemeral roles are possible. Essentially, IAM roles implement RBAC concepts but focus on temporary access.

What challenges can arise when using RBAC ? Can you combine and ABAC?

Over time, RBAC can lead to role explosion, where too many specialized roles are created. This makes it difficult to track who has access to what and can complicate permission management if not carefully maintained.

ABAC, RBAC, ReBAC

TL;DR

  • Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) are common ways to design permissions within an application. Policy-Based Access Control (PBAC) is a method of implementing access control – whether RBAC or ABAC – where you decouple your policy from your code.

  • RBAC ties permissions to roles like Owner, Editor, or Viewer that directly map to permissions. It’s easy to start with, but it creates “role explosion” and becomes rigid as systems grow.

  • ABAC introduces flexibility by factoring in user attributes, resource metadata, and context, such as time or location. It enables dynamic decisions but is harder to manage, test, and optimize at scale.

  • PBAC takes central control by using explicit, auditable policies that combine roles, attributes, and direct permissions. Tools like Oso enable teams to implement these policies declaratively, keeping authorization consistent and testable across services.

  • Oso also supports ReBAC (Relationship-Based Access Control), which defines permissions based on relationships such as manager-of, member-of, or owner-of. (While this article focuses on RBAC, ABAC, and PBAC, we touch on how ReBAC fits into unified, policy-driven authorization systems here.)

In microservices, authorization logic tends to fragment over time. What begins as a few role checks in one service expands into rules based on user attributes, resource metadata, and environmental conditions like time or region. When these rules are implemented separately across APIs, jobs, and frontends, ensuring consistent access control becomes difficult.

This post explains how to design authorization that remains consistent and maintainable across services. It covers three access-control models: RBAC, ABAC, and PBAC, and how they fit together:

  • RBAC maps users to predefined roles and permissions.
  • ABAC evaluates user, resource, and contextual attributes for more granular control.
  • PBAC centralizes all authorization logic in policies evaluated by a policy engine.

Using Oso and its Polar language, we’ll look at how to define RBAC and ABAC declaratively and enforce them through PBAC for reliable authorization across distributed environments.

Access Control Models That Shape Secure Systems

In cloud-native systems and distributed applications, controlling who can access which resources and under what conditions is a pillar of security architecture and operational discipline. Access-control models provide structured ways to enforce these decisions, ranging from static, role-based assignments to dynamic, attribute-driven evaluations. The two foundational models most organizations rely on are RBAC and ABAC, each addressing different levels of granularity and complexity.

RBAC: Role-to-Permission Mapping for Predictable Systems

Role-Based Access Control (RBAC) assigns users to roles, and each role is associated with permissions. This model is effective when user responsibilities are clearly defined and access requirements are relatively stable. For example:

  • In a project management tool, an Admin can create, update, and delete project configurations, while a Viewer can only read project details.

  • In a cloud management system, an Owner can manage billing and account settings, whereas an Editor can deploy and modify resources but cannot access billing information.

RBAC’s simplicity enables fast onboarding and predictable permission management, but it struggles with complex and evolving environments. When systems span multiple teams, geographies, or tenants, RBAC can lead to role proliferation (a.k.a. role explosion), where dozens or hundreds of roles are required to capture nuanced access conditions. This complexity not only increases administrative overhead but also raises the risk of unintended privilege escalation and complicates audit and compliance processes.

ABAC: Attribute-Based Access Evaluation

Attribute-Based Access Control (ABAC) evaluates access dynamically based on a combination of user attributes, resource metadata, and environmental/contextual parameters. ABAC is ideal for systems that require fine-grained, context-sensitive access control, especially in regulated industries and multi-tenant architectures. Examples include:

  • In a banking application, a Compliance Officer may access transaction records only for accounts they manage, while HR personnel are denied access to financial data.

  • In a hospital system, a cardiologist can access patient records for cardiology cases, but not oncology or neurology patients.

  • Time-bound or location-bound access, e.g., a report is readable only during business hours or only from a corporate network, can be enforced without creating new roles.

ABAC reduces the need for creating numerous specialized roles because permissions are calculated at runtime, considering multiple conditions. However, ABAC introduces policy complexity: each new attribute or condition adds evaluation logic, increasing the challenge of testing, updating, and auditing policies. Careful schema design, attribute validation, and policy governance are critical for ABAC to scale safely.

RBAC vs ABAC at a Glance

Feature RBAC ABAC
Decision Basis Role membership User attributes, resource metadata, contextual/environmental conditions
Granularity Coarse, permissions tied to roles Fine-grained, context-aware, dynamic
Flexibility Limited, requires new roles for nuanced access Policies can adapt to multiple conditions without creating new roles
Complexity Low initially; grows with role proliferation Higher; requires attribute management and policy evaluation logic
Auditability Straightforward; roles and permissions Complex; requires logging decisions based on multiple attributes and contexts
Use Cases Stable teams, internal tools, predictable workflows Multi-tenant SaaS platforms, regulated industries, context-aware access scenarios

RBAC gives you predictable guardrails. ABAC adds context, like “only engineers in the same department can edit logs after hours.” The trouble starts when those rules get baked directly into code. An API checks if role == "admin", a middleware checks department, and the frontend hides a button. Six months later, one service blocks the action, another quietly allows it, and nobody can explain why during an audit.

Beyond Access Control: PBAC as the Authorization Control Plane

PBAC solves this problem of fragmented authorization logic by externalizing access control into a centralized policy engine. Instead of embedding conditions across controllers and interfaces, each service makes a standard authorization query: isAllowed(user, action, resource). 

The policy engine evaluates roles, attributes, and contextual data against defined policies and returns a consistent, auditable decision. This model provides authorization as a service, ensuring uniform and maintainable access control across all systems.

When applied, those policies capture conditions that would otherwise lead to role sprawl or scattered logic:

  • Finance systems often need fine-grained clearance rules. Instead of creating a special “AfterHours-ReportViewer” role, PBAC expresses it directly:
 allow(user, "view", report) if
  user.clearance >= 5 and
  report.type == "EOD" and
  request.time >= "18:00";
  • Multi-tenant SaaS platforms need regional separation for compliance. Rather than duplicating roles per region, PBAC encodes the condition once:
 allow(user, "read", tenantData) if
  user.role == "support" and
  user.region in tenantData.allowedRegions;
  • Collaboration tools must combine ownership and sharing. Instead of complex role hierarchies, PBAC states the logic explicitly:
 allow(user, "edit", doc) if
  user == doc.owner or
  user in doc.collaborators;

Because policies live outside application code, they can be tracked in version control, reviewed via change requests, tested in CI, and rolled out like config. Change the rule once, and every service enforces it immediately. Decision logs give you exact “who did what and why” visibility. And instead of creating endless roles for edge cases, you capture the conditions directly in policy.

PBAC doesn’t replace RBAC or ABAC; it enhances their reliability. Roles still group users, attributes still provide flexibility, but PBAC ensures those rules are applied the same way across APIs, UIs, and background jobs. For large-scale or regulated systems, that shift, from scattered checks to centralized, declarative policies, is what makes authorization consistent, auditable, and easy to evolve.

Architecture: How PBAC Enforces RBAC & ABAC Consistently

In most SaaS systems, authorization logic spreads everywhere.

  • An API route checks role == "admin".
  • Middleware validates department == "Engineering".
  • The frontend hides buttons based on user.clearance.

Individually, these checks work, but when applied across multiple services, they tend to drift. One endpoint denies an action, another quietly allows it, and no one can explain why during an audit.

PBAC fixes this by making all services call the same enforcement layer. Instead of scattering conditions across controllers and UIs, you centralize them in a policy engine and ask:

const allowed = await authz.isAllowed(user, action, resource);

That one decision point enforces RBAC role rules, ABAC attribute checks, and any resource-specific policies you’ve defined.

Flow in a SaaS App

The workflow below illustrates the high-level architecture of the access control system: 

Here’s a detailed flow of how access control is enforced at each step:

  1. Authentication (Identity Provider)
  • A user signs in through Auth0 or OIDC.
  • The IdP issues a JWT that includes their ID, role, and optional attributes (e.g. { role: "collaborator", department: "Engineering", clearance: 3 }).
  1. Policy Engine (Oso)
  • Every request runs through Oso.
  • The engine applies your Polar policies: RBAC rules (roles -> permissions), ABAC checks (clearance vs. sensitivity), or per-resource overrides.
  • Output is always the same boolean: allow or deny.
  1. Backend Enforcement (Next.js API Routes)
  • Each route calls authz.isAllowed() before touching the database.
  • Example: POST /products checks that the user has "create" permission. If not, it fails fast with 403 Forbidden.
  1. Frontend Adaptation (Next.js + Tailwind)
  • The UI queries the same engine (or uses cached decisions).
  • Buttons, forms, and routes are shown or hidden based on the policy decision.
  • Example: a Viewer never sees “Edit” or “Delete,” while an Owner does.
  1. Permission Store (MongoDB)
  • Stores users, roles, attributes, and per-resource grants.
  • Oso pulls from this store when applying policies, ensuring consistent results across API and UI.

Access Control in Action: From RBAC and ABAC to Centralized Policy Enforcement

To see how this works in implementation, imagine a small product management app where different people sign up and start working with resources. Some users are assigned fixed roles, others carry specific attributes, and still others gain permissions through ownership. RBAC and ABAC describe the rules of access, but enforcement becomes messy when those rules are spread across the API, middleware, and frontend. This is exactly where PBAC, implemented here in Polar with Oso as a permission layer, provides a single point of evaluation.

Roles as Guardrails (RBAC)

When Shan signs up, his JWT token includes:

That role maps directly to permissions in a Polar policy:

actor User {}

resource Product {
  roles = ["owner", "collaborator", "viewer"];
  permissions = ["read", "update", "delete"];

  # role inheritance
  "viewer" if "collaborator";
  "collaborator" if "owner";

  # permissions
  "read" if "viewer";
  "update" if "collaborator";
  "delete" if "owner";
}

RBAC is simple and fast, but breaks down once you need rules that depend on context.

Attributes as Context (ABAC)

Bob signs up without a role but with attributes:

When Alice creates Product A, she tags it with metadata:

ABAC rules evaluate those attributes:

user_department(_: User, _: String);
user_clearance(_: User, _: Integer);
product_department(_: Product, _: String);
product_sensitivity(_: Product, _: Integer);
has_permission(user: User, "update", product: Product) if
  user_department(user, dept) and
  product_department(product, dept) and
  user_clearance(user, c) and
  product_sensitivity(product, s) and
  c >= s;

This is more flexible than roles, but managing many attributes across services is error-prone.

Ownership and Sharing (Resource-Level Rules)

When Alice creates Product A, she automatically becomes its owner:

actor User {}

resource Product {
  roles = ["owner", "reader", "editor", "deleter", "sharer"];
  permissions = ["read", "update", "delete", "share"];
  # Track who created the product
  relations = { creator: User };
  # Ownership: creator automatically becomes owner
  "owner" if "creator" on resource;
  # Owner inherits all roles
  "reader"  if "owner";
  "editor"  if "owner";
  "deleter" if "owner";
  "sharer"  if "owner";
  # Map roles to permissions
  "read"   if "reader";
  "update" if "editor";
  "delete" if "deleter";
  "share"  if "sharer";
}

Here’s how the flow is:

1. When a product is created

Your application sends a relational fact linking the creator (Alice) to the new product:

rel(product_a, "creator", alice)

  • The policy says "owner" if "creator" on resource;
  • That means Alice automatically gets the owner role on product_a.
  • Because "owner" inherits all other roles, Alice has every permission:


    • read
    • update
    • delete
    • share

When Alice shares the product with Bob

Instead of granting permissions directly, your app assigns Bob one or more roles on the product by sending role facts:

role(bob, "reader",  product_a)   # Bob can read
role(bob, "editor",  product_a)   # Bob can update
role(bob, "deleter", product_a)   # Bob can delete
role(bob, "sharer",  product_a)   # Bob can share
  • Each of these role assignments maps directly to the corresponding permission inside the Product resource block.

  • For example:


    • role(bob, "reader", product_a) : allows Bob to read product_a
    • role(bob, "editor", product_a) : allows Bob to update product_a:

Centralized Enforcement with Policies

Here’s the pivot: instead of scattering checks, every part of the app asks the same question:

const canUpdate = await authService.isAllowed(osoUser, "update", osoProduct);

Oso evaluates the Polar policies and returns true or false. That single decision includes:

  • RBAC role rules,
  • ABAC attribute checks,
  • Resource-specific overrides.

The same enforcement logic runs in API routes, background jobs, and the frontend UI. No more drift.

Why This Matters

  • RBAC defines predictable baseline permissions.
  • ABAC adds context for real-world flexibility.
  • Resource ownership + sharing gives per-object precision.
  • PBAC ties it all together, not as another model, but as the enforcement layer.

And in this app, Oso is the engine making PBAC practical; policies are written in Polar, versioned in Git, tested in CI, and enforced everywhere through one isAllowed() call.

Applying RBAC and ABAC Through PBAC

RBAC and ABAC define the logic of authorization, but without a consistent policy layer, those rules are often duplicated across APIs, services, and frontends. PBAC (Policy-Based Access Control) solves this by centralizing RBAC and ABAC rules into policies that can be versioned, reviewed, tested, and enforced uniformly. This ensures that the same logic is applied everywhere, reducing errors and making audits reliablet. For example, one service might enforce “Managers can approve invoices up to $10,000,” while another accidentally enforces “Managers can approve all invoices.” Drift like this makes audits unreliable and security gaps inevitable.

This is where PBAC fits in: at the point where you need to ensure rules are applied uniformly across a distributed system. PBAC decouples policy logic from application code and centralizes it into a single decision point, such as an authorization service (isAllowed(user, action, resource)). Instead of each service re-implementing RBAC or ABAC checks, they all delegate the decision to PBAC.

By decoupling logic this way, PBAC provides:

  • Consistency: RBAC and ABAC rules are written once and enforced everywhere.

  • Auditability: every authorization decision records which policy allowed or denied it.

  • Version control: policies live alongside code, can be reviewed in PRs, and tested in CI.

  • Scalability: as teams add services, they don’t need to reimplement rules; they consume the central policy engine.

PBAC isn’t a separate authorization model like RBAC or ABAC. Instead, it’s an approach that organizes rules (whether role-based, attribute-based, or a mix) into policies that are managed centrally. By externalizing rules into policies, PBAC ensures consistent enforcement across services, APIs, and UIs, reducing duplication and preventing inconsistencies.

The real decision, then, is not “Which model should I use?” but “What mix of RBAC and ABAC does my system require, and how will PBAC enforce those rules consistently over time?”

By this point, it’s clear that RBAC and ABAC define the logic of access, while PBAC provides the control plane that makes those rules consistent, auditable, and scalable.

So how does this work in production? Let’s look at a few real-world examples where organizations hit the limits of scattered RBAC/ABAC, and why they turned to a PBAC approach to bring order back into authorization.

Examples: How Teams Built Unified Authorization Control Planes

While this post focuses on RBAC, ABAC, and PBAC, most production-grade authorization systems also incorporate Relationship-Based Access Control (ReBAC), a model that defines permissions based on relationships between entities (for example, manager-of, member-of, or owner-of). Here, PBAC unifies all three: RBAC for roles, ABAC for context, and ReBAC for relationships, evaluated together by a single policy engine.

Below are three production deployments where engineering teams moved from fragmented role checks to a PBAC control plane using Oso. Each shows how structured, policy-based authorization simplifies complexity and scales cleanly across products and architectures.

Oyster: Global HR, Sensitive Data, Many Jurisdictions

Context: Oyster operates a global employment platform in over 180 countries, managing payroll and sensitive PII, as well as internal admin and customer-tenant boundaries.

Constraints. From day one, Oyster required RBAC + ReBAC + ABAC to model hierarchical roles, cross-functional access, and region-specific compliance. Its in-house system soon became brittle, with manual role assignment, duplicated checks, and months-long engineering cycles to add or adjust permissions.

With Oso, Oyster decoupled authorization into Polar policies managed in Oso Cloud. Policies are versioned, reviewed, and deployed independently of app code, with all checks routed through a centralized decision service. Roughly 90 policy files govern more than 10,000 users (internal + external). Oso Cloud handles global, low-latency authorization (< 10 ms) while maintaining regional data-sovereignty compliance.

The end result was:

  • 8× faster role implementation through reusable policy patterns.
  • Flexible addition of roles and conditions without touching app code.
  • Consistent, auditable enforcement across services and jurisdictions.

Webflow: Fine-Grained Enterprise Permissions Without Drag

Context. As Webflow expanded into the enterprise segment, customers demanded granular permissions at the level of CMS collections, pages, locales, and product lines.

Constraints. Its JSON-based, database-tied model didn’t scale. Performance degraded under load, caching was inconsistent, and developer effort ballooned, resulting in weeks lost on rejected authorization changes. Scattered logic across services made reasoning and audits difficult.

Webflow integrated Oso to unify RBAC, ABAC, and ReBAC in one system. Resource blocks define all roles, permissions, and relationships for each resource type, consolidating what had been duplicated logic. Policies are written in Polar and integrated into developer workflows through Oso’s CLI, IDE, and CI tools. The decision engine delivers sub-10 ms checks with near-continuous uptime, and fallback nodes ensure authorization continuity during network outages.

The outcome:

  • Clean separation of concerns, authorization treated as infrastructure.
  • Safer, faster policy iterations with minimal regressions.
  • Unified, fine-grained control aligned with enterprise requirements.

Productboard: Centralized Authorization for Microservices and AI

Context. Productboard serves over 6,000 companies and has transitioned from a monolith to microservices while launching AI-driven products requiring user-scoped visibility.

Constraints. The legacy Ruby authorization layer used hardcoded if checks and couldn’t express custom roles, ReBAC, or field-level rules. Consistency across services and latency at scale were key blockers.

Productboard centralized all authorization logic in Oso Cloud. Each microservice delegates authorize() checks to Oso, which evaluates policies against mirrored relationship data. The same PBAC policies govern both human and AI access flows. The stress tests at enterprise scale, millions of data points per tenant, showed Oso was the only system to meet load requirements with zero errors. In production, authorization runs at < 10 ms p95, ~ 50 ms p99.9, and 99.991 % uptime. AI workflows (RAG-based) filter embeddings and search results through Oso, ensuring AI agents see only authorized data.

The Outcome:

  • 2–3× faster enterprise readiness through centralized policy logic.
  • A single source of truth for permissions across microservices and AI.
  • New revenue opportunities via granular, policy-driven governance with minimal engineering overhead.

The Pattern Behind Scalable Authorization 

In each of these architectures above, expressing roles, attributes, and relationships declaratively and evaluating them through a single policy engine made authorization deterministic, testable, and observable. PBAC doesn’t replace RBAC or ABAC; it composes them, often extending to ReBAC to model ownership and delegation semantics. 

Oso implements this composition at the application layer, where authorization logic must align with domain models and service boundaries. By contrast, managing distributed Rego policies in OPA across many microservices introduces drift, duplicated logic, and operational overhead. Oso’s centralized decision engine eliminates that fragmentation, enforcing consistent authorization semantics across APIs, services, and UIs while maintaining low latency and isolation guarantees..

Oso as a Policy-Based Access Control (PBAC) Layer

The key challenge in authorization is not whether you use RBAC or ABAC; it’s how you enforce those rules consistently without scattering them across your application. That’s what a policy-based approach (PBAC) solves: you decouple authorization logic from application code, write it once as policies, and enforce it everywhere with a single decision point.

Oso gives you that layer through Polar, a policy language designed for application authorization. Polar has first-class primitives for actors, resources, roles, permissions, and relations — the things you actually work with when building app auth. Instead of hardcoding if (role === "admin") checks, you write:

const allowed = await oso.authorize(user, "update", resource);

Behind that call, Oso evaluates your RBAC, ABAC, or per-resource policies consistently.

RBAC in Oso

actor User {}

resource Organization {
  roles = ["viewer", "member", "admin"];
  permissions = ["read", "update", "delete"];

  "read"   if "viewer";
  "update" if "member";
  "delete" if "admin";

  # Inheritance
  "viewer" if "member";
  "member" if "admin";
}

One block defines your entire role, including permission mapping and inheritance. Instead of repeating role checks across controllers, the logic is centralized in policy.

ABAC in Oso

actor User {}

resource Document {
  permissions = ["read", "edit", "delete"];

  # ABAC: decision based on a scalar attribute
  "read" if resource.is_public == true;
}

Rules come from attributes: a document is readable by anyone if its is_public field is set to true. This is an ABAC (Attribute-Based Access Control) policy expressed in Polar.

Per-Resource Policies in Oso

actor User {}

resource File {
  permissions = ["read", "delete"];
  relations = { owner: User };

  # role-to-permission sugar
  "read"   if "owner";
  "delete" if "owner";

  # direct grants
  "read"   if granted(actor, "read", resource);
  "delete" if granted(actor, "delete", resource);
}

Here, ownership and explicit grants are pulled from your DB and enforced as policies. Alice might have delete on File A but only read on File B. This is fine-grained, instance-level control without one-off checks in your code.

Developer Workflow with Oso

To manage RBAC, ABAC, and PBAC effectively, Oso provides tooling across the full dev cycle:

  • Rules Workbench: Use Oso Workbench to model and test Polar rules interactively. Developers can simulate isAllowed(user, action, resource) calls and see which rules match.
  • Local Dev Server: Run the Oso local server to evaluate policies against sample data before deploying. This ensures Polar rules behave as expected in your environment.
  • Pre-commit Hooks + CI: Integrate Oso policy linter/tests in Git hooks and CI pipelines. This automatically blocks invalid syntax, untested rules, or overly permissive policies before they merge.
  • Migration Tools: Use Oso Cloud migration tooling to roll out new or updated Polar policies safely. You can stage, test, and incrementally apply changes without breaking production.
  • Explain/Debug: With Oso’s explain mode, developers can trace why authorize() returned true or false, showing which rule fired or why access was denied.

This workflow ensures that policy complexity scales safely with your application as you start adding more features and complexities.

Conclusion: Authorization as a Declarative Control Plane

Authorization scales only when it’s built into the system architecture instead of being scattered through service code. PBAC defines that boundary: policies are treated as data and evaluated through a single decision layer. This makes access decisions consistent, observable, and verifiable.

Oso implements this model within the application layer, where authorization must run close to domain data and context. Instead of maintaining separate Rego policies for each microservice, Oso provides a unified policy plane with defined semantics, version control, and low-latency evaluation.

Consistency in authorization comes from consolidation, not duplication. PBAC, implemented through Oso, establishes that boundary and enforces it uniformly.

FAQs

  1. Which is better, ABAC or RBAC?

RBAC is usually preferred for its simplicity. It works well when roles and permissions are stable. ABAC introduces flexibility but also more moving parts. Access is based on user, resource, and environment data, which adds overhead as systems and policies grow.

  1. Is RBAC part of ABAC?

No. RBAC assigns access through predefined roles. ABAC uses attributes, user details, resource type, department, or clearance level to decide access. ABAC can model RBAC behavior, but it’s a broader, data-driven approach.

  1. What is the difference between PBAC and RBAC?

RBAC grants access through fixed role-to-permission mappings. PBAC defines policies that combine roles, attributes, and conditions to make runtime access decisions. RBAC works for predictable structures; PBAC handles complex or dynamic environments where access logic changes often.

  1. Is IAM RBAC or ABAC?

 IAM is a framework for managing identities, credentials, and permissions. RBAC and ABAC are models implemented within IAM systems to define how access decisions are made.

ABAC, RBAC, ReBAC

TL;DR

  • Role-Based Access Control (RBAC) is a method of managing user permissions by assigning users to roles, where roles are granted specific permissions to act on resources such as API endpoints, database records, and cloud infrastructure objects.

  • RBAC is important in production environments because it enforces the principle of least privilege, cutting down on security incidents, misconfigurations, and accidental over-provisioning. By restricting each user or service to only the actions they’re authorized to perform, you get a system whose behavior is stable and auditable, you know exactly who can do what, and surprise changes are far less likely.

  • Implementing RBAC is easiest with a centralized access policy engine (for example, Oso) that enforces the same rules across all parts of your application, APIs, microservices, and frontend components. This eliminates scattered access control logic and keeps enforcement consistent throughout the entire stack.

  • RBAC policies must be enforced at the backend (API) to guarantee security and consistency. The frontend (UI) can adapt dynamically to a user’s role, hiding or disabling actions they aren’t allowed to perform, but this is purely for usability. True enforcement always happens at the API layer.

  • Hand-rolling access control logic becomes unmanageable at scale, leading to inconsistencies, poor auditability, and security risks. A centralized RBAC engine like Oso simplifies management by defining a robust policy module so that one need not to reinvent the wheel, making it auditable, scalable, and easier to maintain.

  • When RBAC is implemented through a central policy engine, you can unit-test every permission rule in one place and immediately apply changes across all APIs, microservices, and front-end components. Adding a new role or resource becomes a single policy update instead of dozens of scattered code edits. This approach keeps access decisions consistent, auditable, and predictable as your system grows.

When building internal tools at scale, whether it’s a developer portal, an audit dashboard, or a CI/CD orchestrator, access control quickly stops being a feature and becomes a risk surface. In a past role, I worked on a multi-tenant deployment platform used by dozens of internal teams. A misconfigured authorization rule allowed a junior engineer to deploy to production instead of staging, a classic example of how fragile hand-rolled RBAC can lead to real security incidents. It wasn’t malicious, but it took down a customer-facing API. Most access-related incidents stem not from code defects, but from the absence of proper contextual enforcement and boundary controls. 

In a lifecycle, especially one that’s regulated or customer-facing, the strategic imperative is not whether to adopt RBAC, but how to implement it early without disrupting operational continuity. From developer environments to admin consoles and feature toggles, every internal interface becomes a potential vector for misuse. Engineers, platform teams, and security leaders all want the same thing: to move fast without sacrificing control. RBAC offers a structured way to scale those guarantees.

This post is for DevSecOps leads, product security engineers, and platform architects who’ve either implemented or are planning to roll out RBAC in their stack. We’ll walk through how to do RBAC properly, at the code level, across environments, and with real UI examples, using a sample app we built from scratch: a small e-commerce product-catalog demo implemented with Next.js (Next API routes + React frontend), Node/Express-style handlers, PostgreSQL, JWT authentication, and Oso as the centralized policy engine. Instead of hand-writing permission checks across controllers, routes, and UI components, we implemented policies once in Oso (the .polar file), wired the app to call isAllowed(user, action, resource) from server routes, and then surfaced role-aware controls in the Next.js UI. That means you can see exactly how much boilerplate, edge cases, and brittle logic Oso replaces, things you would otherwise have to design, implement, and test yourself (role mapping, environment scoping, audit trails, revocation, partial evaluation and caching, etc.). Along the way we’ll discuss tradeoffs of hand-rolled logic, show how Oso simplifies role-based access management, and spell out what “enterprise-ready” RBAC looks like in practice.

What Is RBAC?

RBAC (Role-Based Access Control) is an authorization model designed to manage who has access to what in a system. Rather than assigning permissions directly to individual users (which quickly becomes unmanageable), RBAC introduces an abstraction:

  • Users are assigned to roles: Each user inherits permissions by belonging to a role like Viewer, Editor, or Admin.

  • Roles are granted permissions: A role defines which actions (read, edit, delete) are allowed.

  • Permissions are scoped to resources or actions: Permissions apply to specific resources (e.g., product, feature flag) and the actions performed on them.

This creates an access system that scales cleanly as your app grows. For example, a user with the Collaborator role can edit product details or post comments, but they can’t delete products, that action is reserved for Owners. The system doesn’t check for individual user IDs (e.g., “is user123 the product creator?”); instead, it checks the user’s role against the policy attached to that role. By avoiding hardcoded checks like “only the creator of this product may delete it”, you remove scattered exceptions and let Oso enforce consistent rules across every endpoint. When a role’s permissions change, the new behavior applies immediately without rewriting business logic.

Why RBAC is Important

Without access boundaries, everything becomes a potential point of failure: not just the services, but the humans operating them. Teams accidentally ship to the wrong environment, over-provision infrastructure, or expose sensitive data, often because they had more access than necessary.

RBAC enforces the principle of least privilege, which says users should only have the minimum permissions needed to do their jobs. For example, in a product catalog app, a Viewer can read product listings but can’t edit or delete them; a Collaborator can update descriptions or add comments but still can’t remove products; and only an Owner has full control, including deletion. This role separation limits the blast radius of mistakes (a Viewer can’t accidentally wipe data), keeps behavior predictable, and gives security teams confidence that access is consistently enforced without blocking day-to-day work.

Overview of RBAC Architecture

In a well-structured production system, Role-Based Access Control (RBAC) shouldn’t be scattered as if/else checks deep inside application code. It should be treated as its own dedicated service or infrastructure layer, on the same footing as your identity provider and authentication mechanism. RBAC acts as the policy-enforcement counterpart to authentication, while principles such as environment isolation (separating dev, staging, and prod) define the boundaries in which that layer operates. This section walks through how a role-based access control system fits into a modern stack, how it interacts with the rest of the infrastructure, and where each responsibility actually lives in practice.

The following architecture supports fine-grained authorization across a distributed stack:

  1. Identity Provider (e.g., Auth0)

The flow begins with the Identity Provider, such as Okta or Auth0, which handles user authentication. After the user logs in, the Identity Provider issues an identity token (e.g., JWT) containing user information like ID, email, and sometimes role metadata.

  1. App/API

After successful authentication, requests are forwarded to the App/API layer, which acts as the initial entry point for processing the request. The App/API interacts with the Access Policy Engine to check permissions before taking any further action.

  1. Access Policy Engine (Oso/OpenFGA)

This is where role-based access decisions are made. The engine evaluates whether a specific user is authorized to perform a certain action on a given resource (e.g., “Can this user update this document?”). It could be a system like Oso, OpenFGA, or a custom-built solution.

  1. Permission DB (PostgreSQL)

The Access Policy Engine queries the Permission Database to evaluate relationships between users, resources, and actions. For example, it checks whether "User A" has permission to "Update" a specific resource or if "Collaborator" has permission to "Delete a comment." The Permission DB is optimized for fast lookups and stores this mapping information, often populated by role assignments or sync jobs.

  1. Final Decision

The Access Policy Engine processes the permission check and returns a final authorization decision. The App/API then either proceeds with the requested operation or returns a 403 Forbidden error if the user is not authorized to perform the action.


  1. RBAC-Aware Frontend (Next.js)

The frontend, built with Next.js or similar frameworks, adjusts its UI based on the user's roles or permissions. It ensures that only the relevant UI elements (e.g., buttons, forms, pages) are visible or interactive, aligning with the backend's access control decisions to maintain a consistent user experience.

Implementation Principles for RBAC in a Microservices Stack

Beyond the high-level flow, it helps to ground this in a real business use case. Assume a product catalog app where different users, Owners, Collaborators, and Viewers, need different levels of access to products and comments. Owners can add or remove products, Collaborators can edit and comment, and Viewers can only browse. On the surface, this sounds simple, but if you tried to hardcode these rules into every API route, frontend component, and database query, it would quickly become complex, brittle, and time-consuming to maintain.

That’s why we built this demo app with Oso. Instead of reinventing the wheel or writing a custom authorization framework from scratch, we used Oso’s open-source policy engine to centralize and declare all role logic at once. The app itself is deliberately lightweight, Next.js frontend, Node/Express-style API routes, and PostgreSQL for persistence, but it demonstrates how even a “small” product can benefit from a dedicated RBAC layer. With Oso handling the rules, the implementation becomes practical, auditable, and easy to extend, which is what you need in a production microservices stack.

1. Identity and Authentication

Every access control system begins with identity. Users must be authenticated before any permissions can be evaluated. Most production systems rely on an Identity Provider (IdP) such as Okta, Auth0, or an internal OIDC-compliant service. Once a user is authenticated, the IdP issues a token (typically a JWT) containing identity claims like user ID, email, organization ID, and sometimes role identifiers. These identity claims are then passed downstream to any service requiring authorization logic.

In our demo app, this process starts at the login page (screenshot above). A user signs in with their email and password, the app verifies their credentials, and a JWT is issued containing their user ID and assigned role (Owner, Collaborator, or Viewer). That token is then included in all subsequent requests to the backend, where Oso evaluates whether the user is allowed to perform the requested action.

2. App/API Layer

After authentication, requests flow into the App/API, which acts as the main entry point for business logic. At this stage the application does not decide permissions on its own. Instead, it forwards the user identity and requested action to the authorization layer. This separation avoids oversized, permission-stuffed tokens and keeps enforcement auditable.

Oso’s recommended patterns include:

  1. Let services fetch authorization data as needed. Each microservice requests permission info when required, rather than relying on outdated or oversized tokens.

  2. Use an API gateway to forward identity context. The gateway only handles authentication and passes identity, not permissions, to services, letting them evaluate access with fresh policy data.

  3. Centralize authorization logic in one service. Services decide whether an action is allowed by calling a shared policy engine (like Oso) that combines roles, resource context, and environment data.

This approach ensures decisions reflect the most current permissions, avoids token bloat, and keeps your authorization logic auditable and maintainable across services.

If a user with access Y is trying to access resource X, it throws an access denied message.

The key advantage of having a separate RBAC engine is that the logic becomes reusable, auditable, and environment-aware. Whether you’re calling it from a backend route, an internal tool, or a CLI-based automation script, the permission logic remains consistent. This separation of concerns also makes it easier to manage and evolve your role based access control model over time, as permissions change or new roles are introduced.

Here, we showed how that token flows into backend routes, using Postman to test requests like “view product” or “edit product.” Those tests made it clear that identity alone isn’t enough, we still need a consistent way to decide what that user can do once authenticated.

That’s where the RBAC engine comes in. The key advantage of separating authorization into its own engine is that the logic becomes reusable, auditable, and environment-aware. Whether you’re calling it from a backend route, an internal tool, or a CLI-based automation script, the same permission logic applies everywhere. This separation of concerns makes it easier to evolve your RBAC model over time, as roles change or new ones are introduced.

3. Access Policy Engine (Oso/OpenFGA)

The policy engine is where authorization actually happens. Given (user, action, resource), it evaluates whether the operation is permitted based on centrally defined policies. For example, Oso lets you encode:

allow(user, "update", product) if user.role = "collaborator";
allow(user, "delete", product) if user.role = "owner";

On the frontend, the app doesn’t enforce authorization, but it still needs to mirror backend rules for a smoother user experience. For example, if a user is a Viewer, the “Edit Product” button should already be greyed out rather than throwing a 403 after they click it. This prefetching of effective permissions ensures the UI matches what the backend enforces, reducing errors and confusion.

4. Role and Permission Store (e.g., PostgreSQL)

A persistent store underpins the policy engine, tracking user-role assignments and mapping them to resources. This store (often backed by PostgreSQL or similar) captures relationships like role inheritance, project scoping, or environment-specific restrictions. Defining permissions declaratively in version-controlled policy files provides auditability, clarity, and maintainability.

This store also plays a critical role in providing auditability. By tracking which roles exist, what permissions they hold, and how they’re used, the system can generate logs and reports for compliance and debugging purposes. Every permission decision can be traced back to a clearly defined rule or role mapping, reducing ambiguity when things go wrong.

5. Final Decision

After evaluation, the policy engine returns an allow/deny decision. The App/API enforces this outcome: if permitted, the request continues; if not, it returns a 403 Forbidden. Every decision can be logged, giving you an audit trail of who tried to do what and when.

6. RBAC-Aware Frontend (Next.js)

While the frontend may receive minimal role or identity information (e.g. "role": "collaborator"), it should never enforce access control directly. Instead, the UI adapts behavior, such as hiding buttons or disabling actions, for better user experience.

However, all authorization decisions must still be enforced in the backend, where the service calls into Oso’s policy engine (e.g. isAllowed(user, action, resource)) to validate whether the requested operation is permitted. This guarantees consistency, auditability, and prevents misuse via tools like Postman or cURL.

By avoiding client-side permission caching or local storage of permission hashes, the system maintains a single, centralized source of truth for all access logic: declarative, testable, and enforceable.

Up to this point, we’ve focused on the app-level flow, login, backend checks with Oso, role storage, and a frontend that adapts to permissions. That gives us a clear view of how RBAC works within a single application. But in production, you rarely have just one app. You’re running multiple services behind gateways, identity providers, and caches.

To understand how centralized RBAC holds everything together in that larger picture, let’s step back and look at how it fits into a modern microservices architecture.

How Centralized RBAC Fits Into a Modern Architecture

This diagram represents a role-based access control (RBAC) system architecture implemented across a microservices-based backend, with an API Gateway, Authn Server, Token Store, and local caching at multiple layers. Here's a step-by-step explanation of how it works:

1. User Logs In & Receives Identity Token

The user authenticates through an Identity Provider (IdP) like Okta or Auth0. Upon successful login, the IdP issues an identity token (typically a JWT) containing basic user claims, such as user ID and email, but not full access rights or roles. This token is strictly used for authentication, not for enforcing permissions.

2. Authorization Happens at the Service Layer, Not the Gateway

The API Gateway acts as a routing layer. It forwards requests to the relevant backend microservices without making any authorization decisions itself. Unlike traditional architectures that rely on token-scoped permissions at the gateway level, this model ensures that all access control logic is centralized and auditable.

3. Microservices Query Oso for Access Decisions

When a service (e.g., Inventory Service, Product Service) receives a request, it performs an authorization check by invoking isAllowed(user, action, resource) on Oso. Oso evaluates the request against its .polar policy file to determine whether the action should be permitted.

This dynamic, runtime evaluation ensures that:

  • Permissions are always up-to-date

  • Access rules are centrally defined and enforced

  • There's no risk of permission drift across environments

4. Optional: Caching at the Policy Layer (Not Gateway)

To improve performance, caching is employed inside the authorization engine (e.g., Oso Cloud) for stable parts of policy execution (such as policy compilation) so redundant work is minimized. Some optimizations include visibility into policy versioning (so you can see when policies update) and support for time-based access expiry.

For example:

  • The cache key isn’t just (user, action, resource) but also the policy version or relationship graph version used to compute the decision. That way, if a policy changes or a relationship is updated, the cache can be invalidated or refreshed automatically.

  • The service can merge cached facts with fresh facts. E.g., if it already knows the user’s roles but needs a fresh attribute from a DB, it can combine them before evaluation, something a gateway cache can’t do without understanding the policy model.

  • The service can implement smart TTLs or event-driven invalidation based on policy/relationship changes. From the outside you’d have to guess, but inside the authz service you know exactly when something makes cached decisions stale.

5. UI and API Stay Synchronized

Since all authorization decisions are made per request and per resource by Oso, both the frontend and backend operate off the same access logic. The UI can query what the current user is allowed to do and render controls accordingly, while the API enforces those rules regardless of frontend behavior.

Contemporary security architectures abstract authorization into policy engines, eliminating hand-written, route-level access checks:

  • Role mapping per microservice

  • Vector DB filters for RAG-based access control

  • Ad hoc policy checks inside LLM toolchains

Instead, you plug into a declarative, unified access layer, like Oso, and make authorization an API, not a part of your codebase. This architecture eliminates scattered permission checks and gives you a scalable foundation for consistent access enforcement across services and interfaces.

Why You Shouldn’t Hand-Roll Authorization Anymore

But even with the right architecture, how you define and enforce policies still determines success. Here's why centralized, declarative authorization is now the baseline, and why hand-rolled logic no longer cuts it.

1. Why Hand-Rolled Authorization Always Breaks First

Authorization used to be baked directly into the application layer. Every microservice had its own version of role mapping. Routes were guarded with one-off isAdmin checks. Access was defined in YAML files, controller logic, and conditional middleware scattered across codebases. It may have worked in the early days, but once you’re managing multiple services, environments, and personas, that approach turns into a liability. There’s no single source of truth, no audit trail, and no consistency across the stack.

As soon as new components like internal admin panels, agent-based automation, or AI orchestration chains are introduced, the old model breaks entirely. Each part of the system ends up reinventing access control in its own silo. And that makes it impossible to reason about, or enforce, security at scale.

2. Centralized Policy Is the New Baseline

Modern authorization is externalized into a unified access layer. Instead of encoding roles and permissions directly into app logic, you define policies declaratively, using a tool like Oso, OpenFGA, or Cedar. These tools let you describe RBAC rules in a structured language. You define roles, permissions, relationships, and scopes in a way that’s consistent and testable. Then you integrate the policy engine as an API that your services, tools, and agents can call in real time.

You don’t repeat logic across your backend, frontend, and automation layers. Access decisions are made programmatically and deterministically, eliminating ambiguity and ensuring policy fidelity. This approach turns authorization into infrastructure, not a set of scattered decisions.

Step-by-Step: Implementing the RBAC Layer

The project is structured around a classic fullstack architecture: Express.js for the backend API, PostgreSQL as the database layer, and a frontend UI built with Next.js. We chose this stack to demonstrate how RBAC enforcement can be cleanly embedded into real-world app layers, not just as a backend filter but as an end-to-end access boundary. The user management flow includes registration, login, and protected routes, all wired to a simple JWT-based session system. Authentication happens first, and role-based authorization follows immediately after.

1. Define roles & permissions (claims vs. enforcement)

Users are assigned one of four roles, owner, collaborator, viewer, developer, persisted in PostgreSQL. At login, the role is issued as an identity claim in the JWT so the frontend can render the right controls. We never authorize the token. On every request, the API loads the user & target resource from PostgreSQL and asks Oso, via isAllowed(user, action, resource). This keeps identity in the token and authorization in policy. (See Oso request-level enforcement.)

The owner role grants full permissions, including viewing, commenting, editing, and deleting any product in the system.

The collaborator role allows the user to view and comment on products, and make limited edits, but lacks deletion privileges.

The user is assigned the viewer role, which grants read-only access across the application without permissions to comment, edit, or delete.

The developer role is a special-purpose role with scoped access for debugging or staging environments, not intended for production changes.

2. Wiring Oso into the Backend Authorization Flow

At the core of our access control system is Oso, a policy engine that defines access logic in a .polar file.

Each permission check boils down to one question:

“Is this user allowed to perform this action on this resource?”

To evaluate that, we:

  1. Retrieve both the user and the resource from PostgreSQL.

  2. Wrap them into domain-specific objects (OsoUser, OsoProduct).

  3. Invoke the isAllowed() method via our authorization service wrapper.

Oso checks the policy rules and returns yes/no, no conditional branching or role checks cluttering the controller logic.

# User roles
allow(user, "read", _product) if user.role in ["owner", "collaborator", "viewer", "developer"];

allow(user, "create", _product) if user.role = "owner";

allow(user, "update", _product) if user.role in ["owner", "collaborator"];

allow(user, "delete", _product) if user.role = "owner";


# Comment permissions
allow(user, "comment", _product) if user.role in ["owner", "collaborator"];

allow(user, "read", _comment) if user.role in ["owner", "collaborator", "viewer", "developer"];

allow(user, "delete", comment) if 
  user.role = "owner" or 
  (user.role = "collaborator" and comment.authorId = user._id);

# User management (only owners can manage users)
allow(user, "manage_users", _) if user.role = "owner";

3. Enforcing Access Across Frontend and API

Once the authorization engine is in place, we connected the frontend to reflect the same access rules visually. After login, users are shown their role in the profile view. Based on that role, certain actions become available or hidden. 

For role-based access control example:

  • Owners can delete products.

  • Collaborators may see an edit button but no delete option.

  • Developers get read/update permissions only in staging and cannot delete in production.

  • Viewers remain strictly read-only.

Policy changes are centrally managed within the .polar file, ensuring consistent, maintainable enforcement.

# User roles
allow(user, "read", _product) if user.role in ["owner", "collaborator", "viewer", "developer"];

allow(user, "create", _product) if user.role = "owner";

allow(user, "update", _product) if user.role in ["owner", "collaborator"];

allow(user, "delete", _product) if user.role = "owner";

# Comment permissions
allow(user, "comment", _product) if user.role in ["owner", "collaborator"];

allow(user, "read", _comment) if user.role in ["owner", "collaborator", "viewer", "developer"];

allow(user, "delete", comment) if 
  user.role = "owner" or 
  (user.role = "collaborator" and comment.authorId = user._id);

# User management (only owners can manage users)
allow(user, "manage_users", _) if user.role = "owner";

These visual controls are purely cosmetic: the real enforcement happens in the backend, but they help create a role-aware interface that prevents user confusion or error. And since permissions are checked again in every API call, there’s no way to bypass access via tools like Postman or cURL.

4. Centralizing and Simplifying Policy with Oso

Instead of scattering role checks across controllers, all access rules live inside authorization.polar. This becomes the single source of truth for permissions.

For example, if we want to give collaborators delete permissions in the future, we’d edit one line in the .polar file, reload the policy, and the entire app respects the new rule, no middleware rewrites needed.

5. Testing the System and Validating Role Behavior

To test the full flow, users can register with different roles either through the /register route in the frontend or directly via the API. 

Once logged in, their role and access level are reflected on the /profile page. From there, they can access /products to view items, attempt edits, or post comments, depending on their permissions. 

This is the main product listing screen where users can view all available products along with their status, category, and creation date.

The comment section allows users to read or add comments for a specific product, with RBAC deciding who can post or delete.

Users with appropriate permissions can access detailed metadata views, showing full metadata like status, creation date, and last modified timestamp.

This modal provides fields for editing product details like name, description, category, and status, editable only if the user’s role permits it.

Only users with the appropriate role (e.g. Owner) see the “Delete Product” action in the dropdown, enforced both in UI and backend.

Every action hits a protected backend endpoint, which consults the Oso engine before proceeding. Unauthorized actions result in a 403 response, and authorized ones continue to the business logic layer. This flow enforces that access control is not optional or assumed, it’s part of the request lifecycle.

Core Concepts in RBAC

When designing access control, roles, permissions, and resources define exactly who can create a project, edit someone else’s comment, or even see that a project exists at all.

In our e-commerce product catalog system, the resource is the Product entity and its associated metadata (comments, visibility status, creation time). Each product lives in a system where access isn’t flat; it varies per user, depending on what role they’ve been assigned.

The actions on these resources include create, edit, delete, comment, and view. For example, an Owner can create and delete projects; a Collaborator can’t create new projects but can still comment and make edits; a Viewer can only observe, no mutations allowed.

We’ve broken the system into clear roles to reflect common collaboration patterns in product teams:

Role View Comment Edit Delete Create
Viewer Yes
Collaborator Yes Yes Yes
Owner Yes Yes Yes Yes Yes

Imagine these roles like access cards in a secure office building. A Viewer has a guest pass, able to walk around and observe, but not touch anything. A Collaborator is like a contractor with a desk, they can make edits, leave notes, and work on existing assets. An Owner holds the master key: they manage the layout, invite others, and remove or reshape the entire structure.

We’re using Oso to formalize these boundaries in code. Oso allows us to declare relationships between users and resources, and then map specific permissions to role levels. So when a request comes in to delete a project, Oso can evaluate: is this user related to this project as an Owner? If yes, allow the action. If not, block it.

What Manual RBAC Actually Looks Like?

Without Oso, you'd be left stitching together authorization logic manually across every part of your stack. Each service would need to implement its own version of access checks, maybe using raw conditionals, a homegrown roles table, or a custom canAccess() function that slowly balloons with edge cases. For every new resource type, you'd have to write fresh logic to define how users relate to it. If you needed to support cross-resource conditions, like “can delete this project only if the user is also an admin of the parent org,” you'd be threading business rules through multiple layers of code.

This file directly shows what authorization looks like without Oso: role checks embedded inside API route logic. It’s exactly the kind of tight coupling and imperative branching that your Oso-based app avoids.

async function handleGetProducts(req, res) {
    try {
        const user = await getAuthUser(req);
        if (!user) {
            return res.status(401).json({ success: false, message: 'Authentication required' });
        }
        
        const allowedRolesToRead = ['owner', 'collaborator', 'viewer', 'developer'];
        if (!allowedRolesToRead.includes(user.role)) {
            return res.status(403).json({ success: false, message: 'Access denied' });
        }

        const { page = 1, limit = 10, search, category, status } = req.query;
        const skip = (page - 1) * limit;

        let query = { isActive: true };
        if (search) {
            query.$text = { $search: search };
        }
        if (category) {
            query.category = category;
        }
        if (status) {
            query.status = status;
        }

        const products = await Product.find(query)
            .populate('createdBy', 'name email')
            .populate('lastModifiedBy', 'name email')
            .sort({ createdAt: -1 })
            .skip(skip)
            .limit(parseInt(limit));

        const total = await Product.countDocuments(query);

        res.status(200).json({
            success: true,
            data: {
                products,
                pagination: {
                    page: parseInt(page),
                    limit: parseInt(limit),
                    total,
                    pages: Math.ceil(total / limit),
                },
            },
        });
    } catch (error) {
        console.error('Get products error:', error);
        res.status(500).json({ success: false, message: 'Internal server error' });
    }
}

As the system grows, so does the complexity. You’d eventually have to build your own policy evaluation layer, your own relationship graph, and your own internal DSL just to keep permissions sane. And even then, there's no standard way to audit what rules exist or test them independently. You're not just maintaining the product, you're maintaining a second, invisible product called "the authorization system," and it's leaking into everything.

What Changes When You Add Oso

Instead of baking permissions directly into each route handler, we now have a centralized engine that knows what every user can and cannot do. This not only prevents unauthorized actions, it also simplifies both debugging and UI logic. As you’ll see later, the frontend can now query what a user is allowed to do before showing buttons or actions. This eliminates the need for embedded role conditionals, promoting centralized governance and auditability.

Oso vs Manual RBAC

Feature Manual RBAC Oso-Powered RBAC
Policy Location Scattered in code (controllers, middleware) Centralized .polar file
Auditability Poor – hard to trace Strong – declarative policies
Consistency Prone to drift across layers Enforced uniformly across stack
Scalability Difficult with new roles/resources Easily extensible
Testing Complex to mock/validate Deterministic and testable
UI Awareness Manual effort, prone to error Queryable permissions for role-aware UI

Conclusion

As your system grows, authorization can’t live scattered across controllers and frontend checks. It needs to be centralized, declarative, and enforced consistently across every layer, API, UI, and automation. That’s exactly what we built: a fullstack RBAC system where access decisions are cleanly separated from business logic and evaluated through a purpose-built policy engine.

Using Oso with Express and PostgreSQL, we formalized user-role-resource relationships in code, enforced permissions at runtime, and exposed a frontend UI that reflects the same logic visually. The result is an architecture that’s not just functional, but scalable, where access rules live in one place, apply everywhere, and evolve cleanly over time.

RBAC isn’t just about restriction, it’s about control. The right model protects velocity while preventing production drift. With a centralized policy layer, your system becomes safer to build on, easier to reason about, and ready for the complexity that comes with scale. Curious how RBAC compares to attribute-based access control? Read our breakdown of RBAC vs ABAC for deeper insight.

FAQs

1. What’s the difference between user-scoped and role-scoped permissions?

User-scoped permissions are assigned directly to an individual, custom, explicit privileges. Role-scoped permissions are attached to a named role, which is then assigned to users. Role based access control models scale better, while user-scoped models offer precision (at the cost of manageability).

2. Can I manage RBAC through a config file?

Yes, for early-stage systems or simple permission models, YAML or JSON config files work fine. Just make sure to reload them safely and avoid hardcoding paths. For dynamic environments or per-project scoping, a DB-backed model with a policy engine (like Oso or Casbin) is more appropriate.

3. How do I test RBAC effectively in CI/CD?

Treat permissions like any other contract. Write unit tests for permission-checking logic, mock role assignments, and assert expected access outcomes. For integration tests, run flows under different simulated users and validate that protected actions are blocked or allowed accordingly.

4. Is RBAC enough for all security needs?

RBAC governs who can access what, but it doesn’t cover everything. For complete security, you still need input validation, rate limiting, secrets management, and infrastructure hardening. Think of RBAC as necessary but not sufficient, a foundational layer, not the whole structure.

ABAC, RBAC, ReBAC

As a developer who's spent years wrestling with access control systems, I can tell you that traditional Role-Based Access Control (RBAC) approaches often fall short when your app needs to grow beyond basic user roles. That's where Attribute-Based Access Control (ABAC) comes to the rescue.

ABAC lets you define access control using attributes of users, resources, and context instead of fixed roles. Think of it as the difference between saying "only admins can edit" versus "users can edit if they're the owner AND the document is in draft status AND it's during business hours."

Sound complicated? It doesn't have to be. Let me show you five practical ABAC examples that'll make your access control both smarter and more maintainable.

TL;DR

ABAC uses attributes (user properties, resource metadata, contextual data) to make fine-grained access decisions. It's more flexible than pure RBAC and perfect for complex business logic. Tools like Oso make implementation straightforward with policy-as-code approaches.

ABAC Policy Examples

Example 1: Conditional Admin Access for After‑Hours Escalations

Scenario: A user can only perform admin actions on resources if they're a support manager and it’s after hours.

Attributes: user.role, hour (current time passed as a context fact)

This is a conditional, time-based admin pattern: support managers get elevated rights only after hours to handle escalations. Instead of handing out permanent admin, you flip it on during the escalation window and let policy turn it off automatically when business hours return—tight control, smaller blast radius, and no manual toggling.

actor User { }

resource Tenant {
  roles = ["member", "support_manager", "admin"];
  permissions = ["read", "manage"];

  "member" if "support_manager";
  "member" if "admin";

  "read" if "member";

  "manage" if "admin";
}

declare current_hour(Integer);

has_role(user: User, "admin", tenant: Tenant) if
  has_role(user, "support_manager", tenant) and
  current_hour(hour) and
  hour matches Integer and
  hour >= 17;

test "support managers get admin access after 5pm" {
  setup {
    has_role(User{"alice"}, "support_manager", Tenant{"tenant1"});
    has_role(User{"bob"}, "member", Tenant{"tenant1"});
    current_hour(18);
  }

  assert has_role(User{"alice"}, "admin", Tenant{"tenant1"});
  assert_not has_role(User{"bob"}, "admin", Tenant{"tenant1"});
}

Why it fits: Support managers get just-in-time admin only after hours, enforcing least privilege and letting the policy auto‑revoke access when business hours resume. It’s granular without extra complexity, and Oso keeps the time/context check in a single, tweakable rule (on‑call, weekends, regions).

Example 2: Conditional Access Based on Resource Tags

Scenario: A contributor can access a project only if it's tagged as "public."

Attributes: user.role, project.tags

This pattern demonstrates business logic expressed as policy rather than hardcoded conditionals scattered throughout your codebase.

actor User {}

resource Project {
  permissions = ["read"];

  "read" if is_public(resource);
}

test "read access for project is allowed if public" {
  setup {
    is_public(Project{"project1"});
  }

  assert has_permission(User{"alice"}, "read", Project{"project1"});
  assert_not has_permission(User{"bob"}, "read", Project{"project2"});
}

Why it fits: When your product manager decides that "internal" projects should also be accessible to contributors, you update the policy once instead of hunting through dozens of API endpoints.

Example 3: Time-Limited Access for Contractors

Scenario: Contractors can access resources only during their active contract window.

Attributes: user.expiration, now

This is a perfect example of contextual access control – permissions that depend on real-time conditions.

actor User {}

resource Repository {
  roles = ["member"];
  permissions = ["read"];

  "read" if "member";
}

has_role(actor: Actor, role: String, repo: Repository) if
  expiration matches Integer and
  has_role_with_expiry(actor, role, repo, expiration) and
  expiration > @current_unix_time;


test "access to repositories is conditional on expiry" {
  setup {
    # Alice's access expires in 2033
    has_role_with_expiry(User{"alice"}, "member", Repository{"anvil"}, 2002913298);
    # Bob's access expired in 2022
    has_role_with_expiry(User{"bob"}, "member", Repository{"anvil"}, 1655758135);
  }

  assert allow(User{"alice"}, "read", Repository{"anvil"});
  assert_not allow(User{"bob"}, "read", Repository{"anvil"});
}

Why it fits: No more manual disabling of contractor accounts. No more "oops, forgot to revoke access" security incidents. The policy automatically enforces time boundaries.

Example 4: Delegated Access (REBAC-style ABAC)

Scenario: A user can access a resource only if someone else has explicitly shared access with them.

Attributes: resource.shared_with, user.id

While not full relationship-based access control, this pattern uses attribute logic to simulate delegation relationships.

actor User {}

resource Repository {
  permissions = ["view"];
  relations = {
    shared_with: User
  };

  "view" if "shared_with";
}

test "access to repositories is conditional on expiry" {
  setup {
    has_relation(Repository{"anvil"}, "shared_with", User{"alice"});
  }

  assert allow(User{"alice"}, "view", Repository{"anvil"});
  assert_not allow(User{"bob"}, "view", Repository{"anvil"});
}

Real-world application: Think shared documents, temporary project access, or vacation coverage scenarios. Someone with access can grant it to others without involving IT.

Example 5: State-Based Permissions (Workflow Control)

Scenario: A user can only edit a record if it's in "draft" status.

Attributes: resource.status, user.permissions

This is a clean example of tying access to business process state – perfect for content workflows, product pipelines, or any multi-stage process.

actor User {}

resource Document {
  roles = ["viewer", "editor", "owner"];
  permissions = ["read", "edit"];

  "read" if "viewer";
  "viewer" if "editor";

  "edit" if "editor" and is_draft(resource);
  "edit" if "owner";
}

test "edit access to document is conditional on if draft" {
  setup {
    is_draft(Document{"document1"});
    has_role(User{"alice"}, "editor", Document{"document1"});
    has_role(User{"bob"}, "viewer", Document{"document1"});
    has_role(User{"alice"}, "viewer", Document{"document2"});
    has_role(User{"bob"}, "editor", Document{"document2"});
  }

  assert allow(User{"alice"}, "edit", Document{"document1"});
  assert_not allow(User{"bob"}, "edit", Document{"document1"});

  assert_not allow(User{"alice"}, "edit", Document{"document2"});
  assert_not allow(User{"bob"}, "edit", Document{"document2"});
}

Why it fits: Your business logic stays in your policies, not scattered across controllers. When the workflow changes (adding a "review" state, for example), you update the policy, not every piece of code that checks permissions.

Implementing ABAC in Oso

Oso makes ABAC practical by letting you express these complex policies in Polar, a declarative policy language designed for developers.

Here's what makes Oso's approach different:

  • Developer control: Policies live in version control alongside your code
  • Separation of concerns: Business logic stays in policies, not scattered through your application
  • Dynamic enforcement: Policies evaluate in real-time with fresh data
  • Testing support: Built-in tooling for policy validation and edge case testing

Quick implementation example:

# Your application code
if oso.authorize(user, "edit", document):
    # Proceed with edit operation
    pass

The policy handles all the complex attribute checking – you just ask "is this allowed?"

ABAC, RBAC, ReBAC

Role-Based Access Control (RBAC) is an authorization approach that organizes access permissions into roles that can be assigned to users. Intuitively, RBAC seems like a simple system. Our responsibilities within an organization naturally confer different levels of access and corresponding security, so an RBAC should hypothetically be easy to set-up. In theory, a person's position maps to a configured role that grants them the permissions they need.

However, this rosy ideal rarely materializes. In the wild, I've seen organizations implement RBAC with overlapping permissions, bloated roles, or incomplete systems that expose security vulnerabilities and cause maintenance nightmares.

The difference between RBAC success and failure often comes down to design principles and ongoing governance rather than the technology itself. Organizations that implement sustainable RBAC systems follow specific patterns for role design, permission granularity, and system maintenance that prevent the common pitfalls.

In this article, I want to share these best practices that successful organizations adopt to design RBAC systems that actually work in practice, not just in theory.

Adopt the Principle of Least Privilege

The principle of least privilege dictates that users should only be granted the minimum permissions necessary to perform their tasks.

When a new user enters a system, it's critical to understand what the user truly needs to accomplish their tasks. Over-permissioning is a common trap.

For example, I once encountered an organization that automatically granted new marketing team members full administrative access to their customer relationship management (CRM) system, simply because "that's what the previous person had" - a classic case of over-permissioning for convenience. It would have been enough to grant read access to customer data and write access to campaign records.

When increasing someone's access, think carefully about how it impacts others with similar roles, or if there are permissions being granted that are unnecessary for the tasks at hand.

Separate users, roles, and permissions

RBAC doesn't mean users should be locked into a single role. Instead, treat permissions as atomic units, roles as bundles of permissions, and users as having one or more roles. This allows greater flexibility and avoids bloated role definitions.

For example, imagine a support engineer who also occasionally helps with onboarding new customers. Rather than creating a monolithic "Support-Plus-Onboarding" role, assign them both the "Support Agent" role (with permissions for ticket management and customer communication) and the "Onboarding Specialist" role (with permissions for account setup and initial configuration). That way, you can later remove the onboarding role as that process becomes more automated.

Separating the concepts of users, roles, and permissions prevents role sprawl, making it easier to manage permissions as responsibilities change.

Design roles around business functions and change rate

Often, I've seen organizations design roles to match job titles. While this works initially, it's brittle to organizational changes. What happens when someone covers another person's job for a week? Or a new employee wearing multiple hats joins the team? It's easy to break the principle of least privilege by temporarily granting such cases an entire role when they only need some of its permissions.

Instead, design roles around stable business functions that persist even when job titles change or new features are added. Instead of "Sales Manager," try "Customer Data Management." Instead of "IT Admin," try "System Configuration.” Describing roles as business functions means they can remain consistent even as organizational structure shifts.

A broader principle is to adjust granularity of roles to the rate of change of your business. A fast-growing startup benefits from atomic roles like "Invoice Processing" and "Customer Support" that can be quickly reassigned as responsibilities shift. A stable enterprise might use broader roles like "Finance Operations" without constant maintenance overhead.

Roles won’t always be perfect, so use the 80/20 rule as a design test: if 80% of users with a role need 80% of its permissions, your abstraction level is probably right.

Plan for role exceptions, but minimize them

No matter how well designed an RBAC implementation is, I’ve always seen them encounter edge cases. Rather than forcing awkward workarounds, my advice is to plan for controlled exceptions while keeping them minimal.

Before creating one-off roles, I’d consider if the need could become a new standard role that applies to other users. This avoids similar exceptions in the future and reduces tracking overhead.

When exceptions are truly necessary, document them with proper approvals and add expiration times if your system supports it. For example, a support agent needing temporary billing access for a major customer issue should receive a time-limited "Billing Support" role rather than permanent customization.

Finally, schedule periodic reviews to consolidate custom roles back into standard ones as organizational needs evolve. Every custom role is security debt that should be paid down when possible.

Automate to reduce human error

Human error is a major cause of security vulnerabilities. Wherever possible, automate RBAC processes to reduce manual mistakes and operational overhead.

There are many ways to automate RBAC processes. Some examples I’ve seen:

  • Auto-provision roles based on job titles or departments from HR systems so new hires automatically receive appropriate baseline permissions without manual setup delays.
  • Automatically revoke all access when someone is marked as terminated in the HR system
  • Automate access reviews by sending managers quarterly emails that list their team's current roles and permissions, requiring them to confirm or request changes within a deadline. This ensures regular validation without the administrative burden of coordinating manual reviews.

That being said, limit automation for high-risk roles like admin access. Manual approval in cases like these help maintain proper oversight.

Design for scoped roles, not global permissions

One of the biggest mistakes I’ve seen in implementing RBAC is to default to global roles. But "Admin" in marketing shouldn’t mean "Admin" in engineering! Real organizations need scoped roles, not global ones, where the same person has different access levels in different contexts.

To implement this concept, design for User → Role → Scope relationships from the start. This often requires more sophisticated RBAC systems that support contextual roles, not just basic role assignment. For example, a department head may need admin rights in their department, but zero access for others.

Scoped roles reduce blast radius when accounts are compromised and enforce least privilege at scale, providing the right access in the right context while maintaining security boundaries.

Add safeguards for risky permissions

Not all permissions are created equal. Gate different risk levels with appropriate safeguards: require MFA for sensitive data access, implement approval workflows for destructive actions, and add audit notifications for administrative functions. This layered approach prevents both accidental damage and malicious insider activity.

Ensure clear escalation paths when approvals are needed urgently and maintain detailed audit trails for all high-risk operations. For example, production deletions should require manager approval, bulk customer data exports need MFA verification, and password resets for other users should trigger security notifications.

The goal is proportional security. Higher risk operations should get stronger controls without slowing down everyday work.

Build or buy an RBAC system? Default to buy

This is a classic debate I’ve seen play out many times. Companies want to implement their RBAC system internally, thinking “Our requirements are simple, we want full control, existing solutions are overkill and we can build it faster.”

But in my experience, this almost always leads to easily predictable problems. Custom RBAC quickly becomes complex as requirements grow, it lacks security expertise, and creates ongoing maintenance that most teams aren't equipped for.

Buying authorization software that supports RBAC offers benefits that keep paying dividends: ongoing security updates, compliance certifications (SOC2, FedRAMP), integration ecosystems, and proven design patterns. There are few exceptions to this rule - free yourself to focus on your core business instead of reinventing security infrastructure.

Maintain and evolve your RBAC system

RBAC systems reflect the organization they operate in, so when organizations change, so do their RBAC systems. In my experience, it’s best to expect and prepare for change gradually rather than doing big overhauls infrequently.

Establish regular maintenance processes from the start:

  • Implement access recertification where managers periodically attest to their team's access
  • Schedule role audits to consolidate and clean up permissions
  • Create change management procedures for organizational shifts
  • Consider implementing proper UI/UX for day-to-day operations.
  • Build monitoring into your RBAC operations by tracking role usage, identifying permission drift, and setting up alerts for unusual patterns.

Without maintenance, even well-designed RBAC systems decay into security liabilities. Your RBAC system should evolve with your company, or else it may eventually constrain it.

Prepare your incident response playbook

Security incidents are a fact of life, but preparation can make all the difference when they happen. I’ve been in rooms with playbooks and rooms without them—trust me, they make all the difference.

RBAC security incidents have unique characteristics: compromised privileged accounts, insider threats abusing legitimate access, and privilege escalation attacks where attackers use existing permissions maliciously. I’ve seen the following preparations work well for these scenarios.

  • Maintaining comprehensive audit trails. ****Log permission grants, revocations, role changes, and access attempts with timestamps, user details, and justifications. These are essential for forensic investigation during incidents and required for compliance frameworks like SOX, GDPR, and HIPAA.
  • Establishing emergency access revocation procedures to quickly disable compromised accounts
  • Creating lockout triggers for suspicious patterns like unusual locations or bulk downloads
  • Enabling emergency admin access when RBAC systems fail or administrators are unavailable.

Once the playbook is created, be sure to practice it with your team and update it as needed. I’ve been in situations where the the playbook was useless because it was only tested during a live security incident.

Effective incident response planning ensures you can respond quickly and methodically when RBAC-related security events occur - don’t skip it!

Next steps for implementation

Whether you're starting from scratch or improving an RBAC existing system, I believe it’s best to focus on the fundamentals: least privilege, clear role boundaries, and automation where possible. Remember that RBAC is an ongoing operational discipline, not a one-time setup.

If you're looking for a proven solution that implements RBAC best practices out of the box, consider using Oso. It handles the complexity of implementing these patterns correctly, so you can focus on your core business instead of rebuilding authorization infrastructure.

ABAC, RBAC, ReBAC

Role-Based Access Control (RBAC) is one of the most widely adopted authorization patterns in modern web applications, providing a structured approach to managing who can access what resources and perform which actions.

Unlike simpler permission models that assign rights directly to individual users, RBAC introduces roles, or groupings of permissions for particular functions. This creates a more natural fit and proves far more scalable for most organizations as they grow.

While RBAC offers clear benefits, I've seen many organizations struggle with role design decisions, technical integration complexities, and migrating from existing permission systems. Without a structured approach, RBAC implementations often become overly complex or fail to meet security requirements. In this guide, we'll walk you through a proven framework for implementing RBAC effectively.

1. Planning & Assessment

I always begin by conducting a comprehensive audit of existing permissions and access patterns. This reveals not just what permissions exist on paper, but how they're actually being used in practice. You'll often discover shadow permissions, unused access rights, and users who've accumulated privileges over time that no longer align with their current responsibilities.

Next, it’s time to design roles. The key insight to keep in mind is that roles should reflect business functions, not job titles or features. Instead of "Sales Manager," use "Customer Data Management." Instead of "IT Admin," use "System Configuration.” Describing roles as functions means they remain consistent even as job titles and organizational structure shifts.

Next, define permissions. Permissions should be atomic, meaning that they map 1-1 to a particular kind of access or action. Roles can have as many permissions as needed, so don’t be afraid to limit the scope of a permission.

When assigning permissions to roles, remember to apply the principle of least privilege, which states that users should only be granted the minimum permissions necessary to perform their tasks. I once encountered an organization that automatically granted new marketing team members full administrative access to their customer relationship management (CRM) system, simply because "that's what the previous person had" - a classic case of over-permissioning for convenience.

The least privilege principle also means that when individuals are over-permissioned, the right thing to do is to remove those extra permissions. This is no fault of the individual, but it’s easy to feel slighted when one’s permissions are reduced. Get ahead of this inevitability by preparing a comms strategy which prepares folks for permission reduction. Remind them that this is inevitable, and is a reflection of the system’s growing maturity rather than an individual’s performance.

Once you have your roles and permissions defined, map current users to these proposed roles. This exercise often reveals gaps in your role design and helps identify users who will need special handling during migration.

Finally, develop a clear migration strategy. Plan to run both systems in parallel during the transition, start with a pilot group for validation, and create fallback mechanisms to quickly revert if issues arise. For users with unique permission sets that don't fit any role, use temporary individual permissions rather than creating one-off roles. This planning phase typically takes longer than expected, but the time invested here prevents painful rework later.

2. Build vs Buy? Default to Buy

Build or buy is a classic debate I've seen play out countless times. For this situation, my advice is simple: default to buying an authorization platform.

I've watched too many teams say "our requirements are straightforward, let’s build it,” six months go by, and they find themselves struggling with complex edge cases, security vulnerabilities, and maintenance overhead that they never anticipated.

Purpose-built authorization platforms bring immediate value through ongoing security updates and battle-tested design patterns learned from thousands of implementations. Nine times out of ten, it’ll save you time and headache.

The rare exceptions where building makes sense are when you have extreme performance requirements or unusually simple integration needs. In most cases, you'll get better results faster by focusing your engineering talent on your core business rather than reinventing security infrastructure that’s already been perfected.

3. Implementation & Integration

Once you've chosen your authorization platform, the real work begins with integrating RBAC checks throughout your application. You'll need checks not just at the API level, but also in your user interface to hide features users shouldn't access.

So, I start by identifying every API endpoint, screen, and component that needs access control. This often is more comprehensive than teams initially realize.

Building automation for handling new users comes next. This is critical - manual role assignments are error-prone and don't scale. Set up automated provisioning tied to your HR systems or identity provider so that when someone joins the sales team, they’re automatically granted the appropriate roles without IT intervention.

Finally, testing. This is where I’ve seen many implementations fall short, especially in handling edge cases gracefully. Systems can break when users have multiple roles with conflicting permissions, or when role inheritance creates unexpected access patterns. Testing should also cover security, including attempted privilege escalation and other boundary conditions. The goal of testing is to catch authorization failures before they reach production.

4. Deployment & Monitoring

Next, it’s time to roll out your RBAC system to production. Remember that this change affects every single person in the organization, so be sure to reinforce your comms strategy through multiple mediums (email, Slack, etc) for redundancy.

Your comms strategy should also prepare your organization for the growing pains that come with such a transition. There are almost certainly edge cases you missed that will bottleneck work. Accelerating approval processes during the transition and communicating with empathy upfront (”We know we’re going to miss some things, but we’ll do our best to address them as quickly as we can…”) will allow most people to give you the benefit of the doubt.

The deployment itself requires careful orchestration and multiple safety nets. I always deploy with robust fallback mechanisms that can quickly revert users to their previous permissions if critical issues emerge. Start with a phased rollout by deploying to a small user group first, then gradually expand as confidence builds.

Once your system is live, monitoring becomes your early warning system. Set up comprehensive logging of access patterns and policy violations, and configure alerting for unauthorized access attempts. I've found that many security incidents reveal themselves through subtle changes in normal access patterns, so baseline your users' typical behavior early.

Also, automate periodic access reviews by scheduling quarterly emails to managers, asking them to verify their team's role assignments. Business requirements evolve, users change responsibilities, new features launch, and compliance requirements shift. Automated reviews help you catch role drift before it becomes a security risk and ensure your RBAC implementation continues serving your organization's needs effectively.

Finally, always plan for incidents. This might mean temporary elevated permissions for specific users or a break-glass procedure that logs all actions for later review. Documenting emergency access procedures allows you to maintain business continuity without compromising security.

5. Common Pitfalls & Design Patterns

The first and most common failure I see is "admin creep" - the gradual expansion of administrative privileges as users request extra permissions to solve immediate problems. If left unchecked, this natural tendency erodes the role structure you took so much care in developing. Combat this by establishing clear approval processes for role changes, implementing expiration dates for elevated permissions, and regularly auditing who has elevated privileges.

Scope creep for roles is equally dangerous. Teams start with simple roles, then add exceptions and special cases until they're essentially back to individual permission management. I've seen organizations with hundreds of micro-roles that defeat the entire purpose of RBAC. When you find yourself creating roles for single users or adding extensive conditional logic, step back and reconsider your role structure.

Finally, role hierarchies - they can be valuable, but more often than not, I’ve seen them increase complexity and maintenance. If this is unavoidable, start with flat role structures and only add inheritance when you have a clear, time-tested business case. Keeping the hierarchy shallow (2-3 levels max) work well for clear permission escalation patterns like "Read-Only User" → "Standard User" → "Admin."

Moving Beyond RBAC

RBAC provides a solid foundation for most organizations' authorization needs, offering role-based permissions that scale well and align with business functions. However, it struggles with complex relationship-based access patterns, like "users can only edit documents they created" or "managers can approve expenses for their direct reports."

When you encounter these relationship-dependent requirements, consider alternate models such as relationship-based access control (ReBAC) or attribute-based access control (ABAC).

The truth is, no access model is perfect. Thankfully, creating custom access models of all types (RBAC, ReBAC, ABAC) is made easy by platforms like Oso. Oso handles the complexity of implementing these access models with these best practices in mind, freeing you up to focus on your core business rather than rebuilding authorization infrastructure.

ABAC, RBAC, ReBAC

When building modern applications, few decisions impact security, scalability, and user experience as profoundly as how you design access control. Historically, many development teams start with a simple RBAC model and implement the authorization logic in their application code. But as requirements evolve, it often becomes necessary to blend elements of RBAC, ABAC, and ReBAC to meet real-world demands. The idea that these are perfectly distinct models gives the incorrect impression that companies choose one and stick with it, when in reality, teams gradually layer models based on evolving needs.

In this article, we'll examine the three dominant access control models—RBAC, ABAC, and ReBAC—comparing their strengths, limitations, and ideal use cases. We'll help you understand which model (or combination) best suits your application's needs and how to implement it effectively. We’ll also explore how Oso can simplify implementation of fine-grained access control, giving you ultimate flexibility to model your data.

Understanding the Access Control Landscape

Access control is fundamentally about answering a simple question: "Who can do what with which resources?" The way that you answer that seemingly simple question depends on who the users are, how the resources are structured, and what context (i.e. organization, timing, geography) matters.

Role-Based Access Control (RBAC)

RBAC is the most widely adopted access control model. It assigns permissions to roles, and users inherit those permissions when assigned to roles.

With RBAC, users have defined roles that determine the data that they can access. It works best for organizations with well-defined job functions and hierarchies. 

For example, in a content management system:

  • Editors can create and edit content
  • Reviewers can approve or reject content
  • Administrators can manage users and system settings

RBAC is the simplest approach to understand, making it a common starting point when development teams are implementing a “DIY” solution. However, RBAC doesn’t provide the fine-grained controls that most modern applications demand, and as a result, many teams extend their permissions to include relationship-based or attribute-based models. 

Relationship-Based Access Control (ReBAC)

ReBAC focuses on the relationships between entities in your system. Rather than assigning permissions directly or through attributes, ReBAC leverages the connections between users and resources.

Let’s continue with the CMS example from the RBAC section. Let’s say a user creates a folder of documents stored within the CMS. If you have viewer access to the folder, you should have viewer access to all of the documents in that folder. Now we need to implement ReBAC, which means that not only do you need roles, but you also need to organize permissions based on the relationship between resources: in this case, which documents are in which folders.

ReBAC is best suited for scenarios with complex hierarchies and interconnected data. For example, in a document management system, ReBAC can easily express policies like "users can access all documents in projects they're assigned to" without having to tag each document individually.

Attribute-Based Access Control (ABAC)

ABAC takes a more dynamic approach by making access decisions based on attributes of users, resources, actions, and environment. It is the most flexible access model, but also the most complex to implement and maintain.

ABAC allows for highly granular control through conditions and attributes. Extending our CMS example further, you may want to mark some documents “public.” A public document can be viewed by all users, regardless of which folder it’s in. The policy now incorporates the “public” attribute of a document into its authorization logic. 

RBAC vs. ABAC: When Roles Aren't Enough

While RBAC provides a solid foundation for access control, it can become unwieldy as applications grow in complexity. The number of roles required to meet permissioning needs often explodes, creating an operational burden. Or, even worse, roles are insufficiently granular and have more access than they should, creating a security risk. 

The Limitations of RBAC

RBAC works well when access patterns align neatly with organizational roles. However, it struggles with:

  1. Context-sensitive permissions
  2. Temporary access requirements
  3. Fine-grained control needs
  4. Dynamic environments

When these challenges arise, many organizations turn to ABAC. While both RBAC and ABAC secure systems and data, the way that they assign and manage permissions differs significantly.

The Power and Complexity of ABAC

ABAC supports significantly more granular permissions than RBAC by considering multiple factors in access decisions:

  • User attributes (department, clearance level, location)
  • Resource attributes (classification, owner, creation date)
  • Action attributes (time of access, previous actions)
  • Environmental attributes (device security, network location)

Yet this flexibility comes at a cost: ABAC models are harder to reason about than RBAC models. They require more careful planning and can be challenging to audit and troubleshoot. 

ABAC vs. ReBAC: Conditions vs. Relationships

Now let’s explore the other major alternative to RBAC: ReBAC. Both ABAC and ReBAC offer fine-grained access control, but they approach the problem differently.

When ABAC Shines

ABAC excels in scenarios requiring complex conditional logic based on multiple attributes. Consider a sophisticated system where access depends on various factors like time, location, and user status.

For example, ABAC can easily express policies like: "A guest can only order a specific drink after midnight if they're seated at the bar." This combines time attributes, location attributes, and user attributes into a single policy.

The ReBAC Advantage

ReBAC leverages the inherent relationships in your data model to simplify access control. Instead of tagging each resource with attributes, ReBAC uses the connections between entities.

Imagine a user named Jay who has a locker containing various items. With ABAC, you might tag each item with "Owner=Jay." With ReBAC, you simply define a policy: "A guest can access all items inside their own locker." The relationship between Jay, his locker, and the items inside creates a natural access control structure.

ReBAC particularly shines in:

  • Hierarchical systems
  • Social networks
  • Document management
  • Team collaboration tools
  • Any application with complex entity relationships

Comparing the Models: A Practical Analysis

Dimension RBAC
(Role-Based Access Control)
ReBAC
(Relationship-Based Access Control)
ABAC
(Attribute-Based Access Control)
Conceptual Complexity Low Moderate to High High
Best for Role-based organizational hierarchies Relationship-aware access Context-aware permissions
Level of granularity Low; can result in either role explosion or roles with access that is too broad Moderate to High High
Example use case A sales person can view all opportunities A sales manager can view all the opportunities owned by their direct reports A sales manager can view all the opportunities owned by their direct reports during business hours on their company laptop
DIY Effort Low to Moderate Moderate to High High
Effort with Oso Low Low to Moderate Low to Moderate

Real-World Applications

To sum it up, different models suit different application types:

  • RBAC: Enterprise applications with clear organizational hierarchies
  • ABAC: Systems requiring complex conditional access (financial services, healthcare)
  • ReBAC: Social platforms, document management, and collaborative tools

Hybrid Approaches: The Best of All Worlds

In practice, many sophisticated applications use a combination of models to address different access control needs. These applications use RBAC for broad permissions, ABAC for context, and ReBAC for structure.

RBAC as a Foundation

RBAC provides a solid foundation for coarse-grained access control. It's intuitive for administrators and developers alike, making it an excellent starting point.

Example use case for RBAC: analyst can view reports

Adding ABAC for Flexibility

ABAC can supplement RBAC when you need context-sensitive permissions. For example, a basic role might grant access to financial reports, while ABAC conditions restrict access based on time, location, or report sensitivity.

Example use case for RBAC and ABAC: analyst can view reports during business hours

Incorporating ReBAC for Relationships

ReBAC excels at handling permissions that follow your data's natural relationships. In many cases, RBAC and ABAC policies can be combined with ReBAC-based policies, with hierarchies enforced by ReBAC and more fine-grained access managed by ABAC.

Example use case for RBAC and ReBAC: Manager can access files owned by their team

Combining RBAC, ABAC, and ReBAC

Finally, there are some scenarios where all three models are required.

Example use case: Doctor can access patient files in their department, if on call, and if patient consent is present

Example of combining models with Oso

resource Document{
  roles = ["reader", "editor", "admin"];
  permissions = ["read", "edit", "delete"];

  relations = { folder: Folder};

  # RBAC - document permissions determined by role on document
  "read" if "reader";
  "edit" if "editor";
  "delete" if "admin";

  # Role inheritance: editors are readers; admins are editors
  "reader" if "editor";
  "editor" if "admin";

  # ReBAC - document permissions inherited from folder permissions
  "reader" if "viewer" on "folder";
  "editor" if "writer" on "folder";
  "admin" if "admin" on "folder";

  # ABAC - all users can read public documents
  "read" if is_public(resource);
}

Implementation Considerations

When implementing access control, consider these factors:

  • Performance impact: Complex authorization checks can affect application responsiveness. Oso provides sub 10 ms latency to ensure that permissions queries get an immediate response. 
  • Developer experience: Overly complex models can slow development. Specialized authorization providers like Oso enable development teams to deliver fine-grained access control in a fraction of the time, so that they can focus on differentiated features within their application.
  • Maintenance overhead: Authorization rules need regular review and updates. Oso provides unit tests, logs, and debugging, so you can make a change without breaking things.
  • Scalability: Your model should grow with your application. Oso’s flexible and expressive Polar language lets you start small with RBAC and layer in ABAC and ReBAC as your application’s authorization logic gets more sophisticated.

The Authorization-as-a-Service Approach

Implementing a homegrown ABAC or ReBAC solution often requires building custom logic into your services, syncing role or relationship data manually, and duplicating logic across endpoints. For this reason, many organizations are moving toward authorization as a service solutions like Oso to simplify permissions. 

This approach separates authorization logic from application code, providing:

  • Centralized policy management
  • Consistent enforcement across services
  • Improved auditability
  • Reduced development complexity

With Oso, the various access models become composable: you define roles, attributes, and relationships declaratively in one place, and Oso handles enforcement, testing, and propagation.

Making the Right Choice for Your Application

Selecting the right access control model depends on your specific requirements:

  1. Start with your data model: Understand the entities in your system and their relationships
  2. Identify access patterns: How do users interact with resources?
  3. Consider future growth: Will your access patterns become more complex over time?
  4. Evaluate implementation resources: More complex models require more development effort

Questions to Guide Your Decision

Access models differ in conceptual complexity: RBAC is easiest to reason about, ABAC introduces conditional logic, and ReBAC models complex relationships between users and resources. Ask yourself:

  • Are roles clearly defined and relatively stable in your organization?
  • Do access decisions depend on dynamic factors like time, location, or resource attributes?
  • Are relationships between entities central to your data model?
  • Do you need to support delegation or inheritance of permissions?

In a DIY system, these factors translate directly into implementation effort.

With Oso, we decouple those concerns. You still need to think carefully about which model(s) make sense for your application — but implementing even the most advanced model (like ABAC) becomes dramatically easier. Oso gives you the tools to model relationships, conditions, and roles in a unified, testable, and maintainable way.

Conclusion

The choice between RBAC, ABAC, and ReBAC isn't about finding the "best" model—it's about finding the right fit for your application's needs. Many successful systems use a combination of approaches, leveraging the strengths of each model where appropriate.

As applications grow more complex and interconnected, we're seeing a shift toward more sophisticated access control models like ABAC and ReBAC. These models provide the fine-grained control needed for modern applications while addressing the limitations of traditional RBAC.

Whatever model you choose, remember that access control is a fundamental aspect of application security and user experience. Investing time in designing the right approach will pay dividends throughout your application's lifecycle.

ABAC, RBAC, ReBAC

As applications grow in complexity, traditional Role-Based Access Control (RBAC) often falls short of meeting nuanced authorization needs. Developers face challenges managing permissions that must adapt to dynamic user attributes, resource contexts, and evolving business rules. This gap calls for modern permission management approaches that go beyond static roles to deliver flexible, scalable, and performant authorization.

At Oso, we provide authorization as a service designed to meet these challenges. Our authorization language, Polar, supports multiple access control models—including RBAC, Relationship-Based Access Control (ReBAC), and Attribute-Based Access Control (ABAC)—while integrating seamlessly with your existing databases. Oso offers a deterministic, testable framework with sub-10 millisecond latency that fits the needs of complex applications.

Here’s what we’ll cover:

  • Why RBAC alone no longer suffices for modern apps
  • Modern permission models and their benefits
  • How Oso’s authorization API addresses complexity with low latency
  • Practical examples of implementing flexible permission logic
  • How Oso’s deployment flexibility and database integration empower developers

Why RBAC Alone Isn’t Enough for Complex Applications

RBAC organizes permissions into roles, which are then assigned to users to grant access rights. This model works well for straightforward scenarios but breaks down in fine-grained or context-dependent policies. For example, consider an app where access depends not only on a user’s role but also on attributes like department, project membership, or time of day.

Static roles can lead to role explosion—where the number of roles grows unwieldy as the number of permission combinations increases. This complexity makes managing and auditing permissions difficult, increasing the risk of errors or security gaps.

Modern applications demand permission management that adapts dynamically to user attributes, resource properties, and environmental conditions. This need has driven the adoption of more expressive models like ABAC and ReBAC.

Modern Permission Models Explained

Attribute-Based Access Control (ABAC)

ABAC evaluates access based on attributes of users, resources, and the environment. For example, a policy might allow access only if a user’s department matches the resource’s department and the request occurs during business hours.

This model offers fine-grained control without proliferating roles. It supports complex, context-aware policies that reflect real-world business logic.

Relationship-Based Access Control (ReBAC)

ReBAC bases permissions on relationships between entities, such as users, groups, and resources. For instance, a user may access a document if they are a member of the project team that owns it.

ReBAC is especially useful in social networks, collaboration tools, and multi-tenant systems where relationships define access boundaries.

Policy-Based Access Control (PBAC)

PBAC centralizes authorization logic into policies that specify conditions for access. These policies can combine roles, attributes and relationships, enabling sophisticated rules like “finance team members can access reports only during business hours.”

Centralized authorization policies are essential in modern applications, whose functionality may be defined across multiple application tiers, containerized services and even serverless functions.

Combining Models for Flexibility

Modern applications often blend RBAC, ABAC, and ReBAC to balance simplicity and expressiveness. RBAC handles broad access categories, while ABAC and ReBAC refine permissions based on context and relationships.

How Oso Simplifies Complex Permission Management

Oso supports these models natively, allowing you to express policies declaratively and enforce them efficiently. Here’s how Oso stands out:

  • Low Latency Authorization Service: Oso delivers permission checks in under 10 milliseconds, ensuring authorization does not become a bottleneck even in high-throughput applications.
  • Local Authorization Without Syncing: Oso is able to run authorization logic locally if desired, eliminating the need to sync permissions from a central server. This reduces latency and improves reliability.
  • Seamless Database Integration: Oso can use data from your existing databases at evaluation time, enabling authorization decisions based on live data without duplicating or migrating data.
  • Deployment Flexibility: For organizations that want additional resilience and peace-of-mind, Oso supports a hybrid deployment model. You can deploy a fallback node inside your VPC to ensure service continuity even in the event of an unexpected loss of communication to Oso Cloud. In addition, a private beta of Oso on-prem is available; please contact us for more information.
  • Deterministic, Testable Framework: Oso’s policy language and engine are designed for clarity and debuggability, making it easier to write, test, and maintain complex authorization rules.

Implementing Flexible Permission Logic with Oso

Consider a scenario where you want to allow users to edit documents only if they belong to the same team and the document is not archived. With Oso, you can express this logic clearly:

allow(user:User, "view", document:Document) if   has_relation(user, "team", _team) and   has_relation(document, "team", _team) and   not is_archived(document);


This policy combines user attributes and resource state without creating new roles for every possible combination. Oso evaluates this efficiently, returning a simple yes/no answer.

You can also incorporate relationships, such as allowing access if a user is a manager of the document owner:

allow(user:User, "view", document:Document) if   has_relation(_owner, "manager", user) and   has_relation(document, "owner", _owner);

This flexibility supports evolving business rules without rewriting your entire permission system.

Deployment and Integration: Making Authorization Work for You

Oso’s ability to integrate with your existing Postgres database schema means you don’t have to restructure your data or maintain separate data stores. This reduces operational overhead and keeps authorization aligned with your application state.

Moreover, this lets you run authorization close to your data, minimizing exposure and meeting strict regulatory requirements.

Summary: Moving Beyond RBAC with Oso

RBAC remains a useful foundation, but modern applications require permission management that adapts to complex, dynamic contexts. Models like ABAC and ReBAC provide this flexibility, but implementing them efficiently is challenging.

Oso offers a robust authorization service that supports multiple models, incorporates local application data, and delivers low-latency, local authorization. Its deterministic framework makes policies testable and maintainable, while deployment flexibility meets diverse infrastructure needs.

If you’re building complex apps and want a permission management solution that scales with your business logic, Oso provides the tools and expertise to get it right.

Explore Oso’s documentation to see how you can implement modern authorization in your applications today.

ABAC, RBAC, ReBAC

Introduction

Access control is a cornerstone of application security, ensuring that users only access the resources they are authorized to use. Two widely adopted access control models are Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). While both models aim to secure systems and data, they differ significantly in how they assign and manage permissions.

In this article, we’ll explore the key differences between RBAC and ABAC, their use cases, and how to decide which model—or combination of models—might be the best fit for your organization. Whether you’re looking for a straightforward, role-based solution or a more flexible, attribute-driven approach, this guide will help you make an informed decision.

If you’re short on time, check out the TL;DR section or the video below for a quick summary of the main differences.

TL;DR: Quick Comparison of RBAC vs ABAC

Feature RBAC
(Role-Based Access Control)
ABAC
(Attribute-Based Access Control)
How it works Assigns permissions based on predefined roles (e.g., Admin, Editor, Viewer). Grants or denies access based on user and resource attributes (e.g., location, time, or data sensitivity).
Best For Organizations with stable, well-defined roles and straightforward access needs. Dynamic environments requiring fine-grained access control.
Flexibility Less flexible, as permissions are tied to static roles. Highly flexible, allowing for dynamic and context-aware access policies.
Complexity Simple to implement and manage, but can lead to role explosion. More complex to implement and manage due to the need for attribute definitions and policy configurations, but offers fine-grained control.
Granularity Coarse-grained control based on roles. Fine-grained control based on multiple attributes.
Use Case Example A "Finance Manager" role grants access to budget reports. A user can only access reports during business hours from a company-issued laptop.

What is RBAC?

Role-Based Access Control (RBAC) is a widely used authorization model that simplifies access management by grouping permissions into roles. A role is essentially a collection of permissions that defines what actions a user can perform on specific resources. For example, roles like "Admin," "Editor," or "Viewer" might grant varying levels of access to a system.

When a user is assigned to a role, they inherit all the permissions associated with that role. This makes RBAC an intuitive and efficient way to manage access, especially in organizations with well-defined job functions or hierarchical structures.

Key Features of RBAC:

  • Simplified Permission Management: Instead of assigning individual permissions to users, permissions are grouped into roles, which can then be used to assign multiple permissions to a user at once.
  • Ease of Implementation: RBAC is straightforward to set up and manage, making it a popular choice for organizations with stable, predictable access requirements.
  • Improved Security: By limiting access based on a user’s role, RBAC reduces the risk of unauthorized access and enforces the principle of least privilege.

Example Use Case:

Imagine a content management system where:

  • Admins can create, edit, and delete content.
  • Editors can modify existing content but cannot delete it.
  • Viewers can only read content.

By assigning users to these roles, the organization can efficiently manage access without needing to define permissions for each individual user.

Pros and Cons of RBAC:

  • Pros:
    • Easy to understand and manage.
    • Scales well for organizations with clear role definitions.
    • Reduces administrative overhead by grouping permissions.
  • Cons:
    • Less flexible for dynamic or complex environments.
    • Can become cumbersome if roles proliferate without proper management.

RBAC is often seen as a foundational model for access control. However, as we'll explore later, it can also be viewed as a specific implementation of Attribute-Based Access Control (ABAC), where the "role" is treated as just one of many possible attributes.

For a more detailed explanation of RBAC, including its implementation and use cases, check out our Role-Based Access Control guide.

What is ABAC?

Attribute-Based Access Control (ABAC) is an advanced authorization model that provides fine-grained access control by evaluating a variety of attributes. Unlike RBAC, which relies solely on roles, ABAC considers multiple attributes related to the user, the resource, and the environment to make access decisions.

Key Features of ABAC:

  • Granular Control: ABAC allows for dynamic access policies based on attributes such as user identity, role, department, location, time of access, and more.
  • Flexibility: This model supports complex scenarios where access needs to be adjusted based on changing conditions or contexts.
  • Dynamic Authorization: ABAC policies can be tailored to accommodate specific business rules, making it ideal for environments with evolving access requirements.

Example Use Case:

Consider a healthcare application where access to patient records is controlled based on:

  • User Role: Doctors and nurses have different levels of access.
  • Location: Access is granted only within the hospital network.
  • Time: Access is restricted to working hours.

By evaluating these attributes, ABAC can enforce nuanced access policies that adapt to the specific needs of the organization.

Pros and Cons of ABAC:

  • Pros:
    • Provides comprehensive and adaptable access control.
    • Supports complex policies that reflect real-world scenarios.
    • Enhances security by considering multiple factors in access decisions.
  • Cons:
    • Requires careful planning and management to implement effectively.
    • Can be more complex to set up compared to RBAC.

ABAC expands upon the basic principles of RBAC by incorporating attributes into the mix, allowing organizations to create more granular and dynamic authorization policies. This flexibility makes ABAC particularly suitable for environments where access needs are complex and subject to frequent changes.

For a deeper dive into ABAC, including its benefits and implementation strategies, check out our Attribute-Based Access Control guide.

Real-World Challenges with RBAC and ABAC

While both RBAC and ABAC are powerful models for managing access, they each come with challenges that you should consider when deciding which to use.

Challenges with RBAC

  1. Role Explosion:
    • In large organizations, the number of roles can grow exponentially as more granular permissions are needed. This "role explosion" makes managing roles and permissions increasingly complex and difficult to scale.
    • For example, a company with multiple departments may need separate roles for "Manager," "Team Lead," and "Employee" in each department, quickly leading to hundreds of roles.
  2. Lack of Flexibility:
    • RBAC is rigid and works best in environments where roles and permissions remain stable over time. It’s not well suited for dynamic or context-specific access needs, such as granting access based on time, location, or project status.
  3. Over-Permissioning:
    • Users are often assigned roles that grant more permissions than they actually need, increasing security risks. This happens because RBAC lacks the granularity to fine-tune access for specific scenarios.

Challenges with ABAC

  1. Policy Complexity:
    • ABAC policies can become highly complex as they incorporate multiple attributes (e.g., user role, location, time, and resource sensitivity). Writing and maintaining these policies requires significant effort and expertise.
    • For instance, defining a policy that grants access only to "senior managers in the finance department during business hours" involves multiple attributes and conditions.
  2. Performance Concerns:
    • Evaluating access requests in ABAC can be resource-intensive, especially when dealing with a large number of attributes or complex conditions. This can impact system performance, particularly in high-traffic environments.
  3. Implementation Overhead:
    • Implementing ABAC requires a deeper understanding of the organization's data and workflows to identify the right attributes and conditions. This can make adoption slower and more challenging compared to RBAC.

RBAC vs. ABAC: Key Differences

Both Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) are widely used access control models, but they differ significantly in how they manage permissions. Understanding these differences can help you choose the right model for your organization’s needs.

1. How They Work

  • RBAC: Access is granted based on roles. Users are assigned predefined roles (e.g., "Admin," "Editor," "Viewer"), and permissions are tied to those roles.
  • ABAC: Access is granted based on attributes. These attributes can include user roles, resource types, environmental factors (e.g., time, location), or any other relevant criteria.

2. Flexibility

  • RBAC: Offers a simple and intuitive structure for managing access. It works well in environments where roles are stable and clearly defined.
  • ABAC: Provides dynamic and fine-grained control by evaluating multiple attributes. This flexibility allows ABAC to adapt to changing conditions and enforce nuanced access policies.

3. Complexity

  • RBAC: Easier to implement and manage because it relies on a straightforward role hierarchy. However, it can become cumbersome if too many roles are created.
  • ABAC: More complex to set up and maintain due to the need to define and manage multiple attributes and policies. This complexity is the trade-off for its adaptability and precision.

4. Use Cases

  • RBAC: A retail company assigns permissions based on job roles
    • Store managers can access sales reports and manage inventory.
    • Cashiers can process transactions but cannot access financial data.
  • ABAC: A healthcare system controls access to patient records
    • Doctors can access patient records only during their shifts and within the hospital network.
    • Nurses can view records for patients assigned to their care but cannot edit them.
    • Access is denied if the request comes from outside the hospital’s secure network.

When to Use RBAC vs ABAC

Choosing between Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) depends on your organization’s structure, security requirements, and the complexity of your access control needs. Each model has its strengths and is suited to different scenarios.

When to Use RBAC

RBAC is ideal for organizations with static access requirements and clearly defined roles. It works well in environments where simplicity and ease of management are priorities. Consider using RBAC if:

  • Your organization has stable roles: For example, a company where employees have fixed roles like "HR Manager," "IT Support," or "Sales Associate."
  • You need a straightforward implementation: RBAC is easier to set up and manage, especially for smaller teams or organizations with limited engineering resources.
  • Your access needs are predictable: If permissions rarely change or are tied directly to job functions, RBAC provides a reliable and efficient solution.

When to Use ABAC

ABAC is better suited for organizations with dynamic and complex access needs. It provides fine-grained control by evaluating multiple attributes, making it ideal for environments where access requirements frequently change. Consider using ABAC if:

  • Your organization requires granular control: For example, access policies need to account for attributes like user location, time of access, or the sensitivity of the resource.
  • You operate in a highly regulated industry: ABAC can enforce compliance by tailoring access policies to meet specific regulatory requirements (e.g., HIPAA, GDPR).
  • Your access needs are context-dependent: ABAC can dynamically adjust permissions based on real-time conditions.

Combining RBAC and ABAC for Better Authorization

At Oso, we recognize that modern organizations often require a combination of access control models to meet their unique needs. While RBAC and ABAC are widely used, many organizations benefit from a hybrid approach—combining the simplicity of RBAC with the flexibility of ABAC. For systems where relationships between entities play a key role, they can even incorporate ReBAC (Relationship-Based Access Control). Learn more about ReBAC and how it works here.

When implementing your hybrid model, start with RBAC to define broad permissions, then layer ABAC policies for dynamic access control. For example, assign a 'Team Member' role with baseline permissions and use attributes like project status or user location to refine access.

Why Use a Hybrid Approach?

A hybrid approach allows you to:

  • Simplify baseline access management: Use RBAC to define broad permissions based on roles, such as "Admin," "Manager," or "Employee."
  • Add granularity and adaptability: Layer ABAC on top of RBAC to refine access policies based on attributes like location, time, or resource sensitivity.
  • Balance ease of management with precision: By combining the two models, you can avoid the complexity of managing numerous roles while still enforcing dynamic, context-aware access policies.

This approach is particularly useful in environments where:

  • Roles alone are insufficient: For example, when access needs to account for real-time conditions or regulatory requirements.
  • Flexibility is critical: Organizations with dynamic access needs, such as global enterprises or industries with strict compliance standards, benefit from the adaptability of ABAC.

Example Use Cases for a Hybrid Approach

  1. Enterprise with Regional Restrictions
    • RBAC: Assign employees roles like "Regional Manager" or "Team Lead."
    • ABAC: Enforce additional policies to restrict access to data based on the user’s location, ensuring compliance with regional regulations.
  2. Healthcare System
    • RBAC: Define roles such as "Doctor," "Nurse," and "Administrator."
    • ABAC: Add attributes to control access to patient records based on the user’s shift hours, department, and the sensitivity of the data.
  3. SaaS Application
    • RBAC: Assign roles like "Admin," "Editor," and "Viewer" to manage baseline permissions.
    • ABAC: Use attributes like subscription tier, account status, or geographic location to customize access for specific users.

How Oso Supports Hybrid Models

Implementing a hybrid model can seem daunting, but Oso’s Authorization as a service platform is designed to make it seamless, combining the simplicity of RBAC, the flexibility of ABAC, and even the relationship-driven granularity of ReBAC. With Polar, Oso’s declarative configuration language for authorization, you can define and enforce both role-based and attribute-based logic in a single policy, ensuring efficient and scalable access control.

By adopting a hybrid approach, organizations can achieve the best of both RBAC and ABAC, addressing real-world challenges while maintaining security and flexibility.

Example: Using Polar to Implement a Hybrid Model

Let’s say you’re building an application for a project management platform. You want to combine RBAC, ABAC, and ReBAC to manage access to project resources. Here’s how you could do it with Oso and Polar:

RBAC for Baseline Permissions:

  1. Define roles like "Project Manager," and “Member."
  2. Use Polar to write rules that grant basic permissions based on these roles. For example:
# Define Actor (User)
actor User {}

# Define Resource (Project)
resource Project {
  permissions = ["read", "update", "delete", "member.add", "member.remove"];
  roles = ["member", "project_manager"];

  # Role permissions
  # project managers can do everything members can do
  "member" if "project_manager";

  # project manager permissions
  "delete" if "project_manager";
  "member.add" if "project_manager";
  "member.remove" if "project_manager";

  # member permissions
  "read" if "member";
  "update" if "member";
}

ABAC for Attribute-Based Refinements:

  1. Add attributes like project status (e.g., "Active" or "Archived") and user location.
  2. Refine access policies based on these attributes. For example:
# Define Actor (User)
actor User {}

# Define Resource (Project)
resource Project {
  permissions = ["read", "update", "delete", "member.add", "member.remove"];
  roles = ["member", "project_manager"];

  # Attribute-based permissions
  # All users can read public projects
  "read" if is_public(resource);

  # Role-based permissions
  # project managers can do everything members can do
  "member" if "project_manager";

  # project manager permissions
  "delete" if "project_manager";
  "member.add" if "project_manager";
  "member.remove" if "project_manager";

  # member permissions
  "read" if "member";
  "update" if "member";
}

ReBAC for Relationship-Based Control:

  1. Incorporate relationships between users and resources, such as team assignments or ownership.
  2. Use Polar to enforce permissions based on these relationships. For example:
# Define Actor (User)
actor User {}

# Define Resource (Team)
resource Team {
  roles = ["member"];
}

# Define Resource (Project)
resource Project {
  permissions = ["read", "update", "delete", "member.add", "member.remove"];
  roles = ["member", "project_manager"];

  # A project can be associated with a team
  relations = {team: Team};

  # Relationship-based permissions
  # Members of a team that owns a project can view the project
  "read" if "member" on "team";

  # Attribute-based permissions
  # All users can read public projects
  "read" if is_public(resource);

  # Role-based permissions
  # project managers can do everything members can do
  "member" if "project_manager";

  # project manager permissions
  "delete" if "project_manager";
  "member.add" if "project_manager";
  "member.remove" if "project_manager";

  # member permissions
  "read" if "member";
  "update" if "member";
}

By combining these models, you can create a powerful and flexible authorization system that meets the needs of your application.

Why Use Oso for Hybrid Models?

  • Ease of Implementation: Oso’s Polar language makes it simple to define and enforce complex policies.
  • Flexibility: Combine RBAC, ABAC, and ReBAC seamlessly to handle a wide range of access control scenarios.
  • Scalability: As your application grows, Oso’s tools make it easy to adapt your policies to new requirements.

Conclusion

Choosing the right access control model—RBAC, ABAC, or a combination of control models—depends on your organization’s specific needs. RBAC offers simplicity and ease of management, making it ideal for environments with stable roles and predictable access requirements. On the other hand, ABAC provides the flexibility and granularity needed for dynamic, attribute-driven scenarios.

For many organizations, a hybrid approach that combines RBAC and ABAC delivers the best of both worlds. By using roles as a foundation and layering attributes for fine-grained control, you can create an access control system that is both powerful and easy to manage. Additionally, incorporating models like ReBAC into a hybrid solution can further enhance your ability to enforce relationship-driven permissions.

At Oso, we make it easy to implement these models with tools like Polar, enabling you to define and enforce policies that meet the demands of modern applications. Whether you’re managing a small team or a global enterprise, Oso provides the flexibility and scalability you need to secure your systems effectively.

Ready to take your authorization to the next level? Explore our Authorization Academy or learn more about how Oso can help you build a robust access control system.

ABAC, RBAC, ReBAC

Attribute-based Access Control (ABAC for short) is a pattern for determining whether a user is authorized to perform a certain action based on attributes associated with the actor or the subject of the action.

ABAC is a broad pattern that is a superset of many other authorization patterns, like role-based access control (RBAC) and relationship-based access control (ReBAC). Both RBAC roles and ReBAC relationships can be thought of as attributes of the actor and the subject.

In this blog post, you will learn what ABAC is, the differences between ABAC and other access control models, and how you can implement ABAC with Oso Cloud.

How does ABAC use attributes to express access control rules?

Authorization can be represented as a question: does the given actor have permission to perform a certain action on a given resource. An attribute is any property that is associated with the actor, the action, or the resource. You can think of an attribute as a column in a SQL database.

For example, consider GitHub repositories. Repositories have an is_public attribute: if a repository is public, any actor can read the repository. Users have an is_copilot_subscriber attribute: if a user has a paid subscription, they can access GitHub copilot. These are both examples of attribute based access control policies.

What are the differences between ABAC vs RBAC?

ABAC is a broad, almost vague pattern that encompasses just about any authorization pattern. RBAC can be represented as a subset of ABAC where the only attribute you consider is the actor’s role. When you’re building a new app, you’re more likely to use RBAC than ABAC when you first introduce authorization. Because roles are easier to compose and easier to reason about, particularly when they line up with real world job titles and roles.

At Oso we believe that the standardized nomenclature the industry has ascribed to authorization is limiting. Firstly, ABAC, RBAC and ReBAC are all related. The higher order models are like Russian dolls, subsets of different types of access control patterns, and also subsets of each other. ABAC encompasses all access control models effectively (“Anything can be expressed as an attribute!”); ReBAC is a subset of ABAC; and RBAC is a subset of ReBAC.

Authorization Patterns as Russian Dolls

Additionally, when you’re implementing access control, you’re often implementing a pattern which is a subset of RBAC, ABAC or ReBAC. At Oso, we’ve documented ten of the most common types of access control that we’ve seen with our customers.

How do I implement ABAC?

Oso Cloud has several neat features that can help you implement ABAC. If you haven't already, create a free Oso Cloud account and log in. For the purposes of this tutorial, we will have one type of actor, a User, and one type of resource, a Repository. Below is how we can represent these entities in Polar, Oso's declarative authorization language.

Let's add an is_public attribute to repositories, and a rule that says users can read repositories that are public.

Navigate to Oso Cloud's "Explain" tab, which lets you simulate authorization queries. By default, the user Bob can't read the repository website because the repository website does not have is_public set.

In Oso Cloud, facts are how you represent authorization data, including ABAC attributes. You can represent whether a repository is public as a fact. Navigate to the "Data" tab in the Oso Cloud UI, and add a new fact that indicates that the repository website has is_public set to true.

Now, navigate back to Oso Cloud's "Explain" tab. Now that the repository website has is_public set to true, Oso Cloud can infer that the user Bob can read the repository website.

The “users can read public repositories” rule is an example of the “Attribute checks” pattern from Oso’s 10 Types of Authorization blog post. Another ABAC pattern from that blog post is called “Attribute add-ons”, which mixes ABAC patterns with RBAC and ReBAC. For example, the following rule tells Oso that “users can write public repositories if they have the member role.”

What is an example of ABAC in practice?

One of the most common places developers see ABAC in practice is AWS ABAC, which allows developers to write authorization rules for AWS resources using AWS' custom syntax. AWS ABAC is an example of the “Custom policies” pattern from Oso’s 10 Types of Authorization blog post because it allows developers to define arbitrary policies.

For example, the following JSON defines an AWS policy that allows actors (AWS calls actors principals) with tag "project" to invoke Lambda functions with tag "project". In this case, a tag is an attribute on both actors and resources.

The example shows why ABAC is such a blanket definition. Rename “tag” to “role” and the same logic still works: actors with role “admin” can invoke any Lambda function with tag “project”. So this AWS ABAC example can be represented as a mix of RBAC and ABAC.

See our guides for additional ABAC patterns, like Public and Private repositories and Entitlements.

Moving on

RBAC and ABAC are two of the most popular authorization patterns. ABAC is more powerful and flexible, but RBAC is similar and easier to work with. With Oso Cloud, you don't have to chose between RBAC and ABAC: Oso Cloud makes it easy to implement both when you need to. So sign up for Oso Cloud.

ABAC, RBAC, ReBAC

When building applications with user permissions, Role-Based Access Control (RBAC) provides a structured way to define who can perform specific actions. Instead of scattering authorization logic throughout your codebase, RBAC organizes permissions into roles that determine access across different parts of an application. This guide walks through implementing RBAC in a Node.js application using Oso Cloud.

It covers defining role-based rules, and filtering data based on user roles. Whether managing organizational roles, resource-specific roles, or cross-organization access, this approach ensures consistent authorization decisions across your system.

What is Role-Based Access Control (RBAC) in Node.js?

Roles are a common way to simplify authorization logic for engineers and users. Authorization logic that’s based on roles is called “role-based access control."

role is a way to group permissions. When a user is assigned a role, the user gets every permission that the role has.

permission is an action that a user can take on a resource. For example, we might say that a user in an organization has permission to read repositories.

There are a number of variations on role-based access to allow for more flexible groupings of permissions, like:

a. Organizational roles

b. Cross-organization roles

c. Resource-specific roles


Oso’s Node SDK provides tools for defining and enforcing RBAC policies. It supports declarative policy definitions and integrates with existing applications. Oso can be used for new implementations or to enhance existing authorization systems.

For detailed implementation guidance, refer to the Node SDK documentation.

4 reasons to build RBAC in Node with Oso:

1. Oso Cloud is fully-managed and deployed across multiple regions for low-latency and high availability.
2. Oso Cloud comes out of the box with primitives for role-based access control (RBAC). It also includes built-ins for other access control models like relationship-based access control (ReBAC) or attribute-based access control (ABAC).

3. You provide Oso Cloud with the requisite authorization data, then your RBAC policy operates that data to make authorization decisions at runtime.
4. Oso can provide yes/no authorization decisions, as well as filter lists of data.

Express RBAC in Node with Oso Cloud

To authorize whether a user has the role required to perform an action on a resource, call Oso in your controller.

// This will return `false` if the current user does not
// have access to the Repository that they're trying to read
const user = {id: User.getCurrentUser(), type: "User"};
const repo = {id: repoName, type: "Repository"};
await oso.authorize(user, "read", repo);


You’ll also write an Oso policy—that is, a set of rules—to implement role-based authorization. Here, we’ll show a policy for an app for source code hosting like GitHub or GitLab.

In this policy, users may or may not be able to read or make changes to a repository, depending on whether they’re members or owners. That means we need authorization based on users’ roles.

actor User {}

resource Organization {
 roles = ["owner"];
}

resource Repository {
 permissions = ["read", "push"];
 roles = ["contributor", "maintainer"];
 relations = { parent: Organization };

 # An actor has the "read" permission if they have the "contributor" role.
 "read" if "contributor";
 # An actor has the "push" permission if they have the "maintainer" role.
 "push" if "maintainer";

 # An actor has the "contributor" role if they have the "maintainer" role.
 "contributor" if "maintainer";

 # An actor has the "maintainer" role if they have the "owner" role on the 
 # "parent" Organization.
 "maintainer" if "owner" on "parent";
}


To learn how to model RBAC with Oso, take a look at our RBAC modeling guide.

For a guide on other authorization models, such as ReBAC or ABAC, check out our authorization modeling guide covering roles, hierarchies, groups, and other patterns.

Filter data based on a user’s role

Your app needs to be able to return all the repos that a user can see based on their role and any other relevant criteria. To do this we can use the list method.

Here's that in the Node app again:

const user = {id: User.getCurrentUser(), type: "User"};
let repos = await oso.list(user, "read", "Repository");


Learn how to implement authorization in Node.js

Key steps for implementing authorization in Node.js is as below:

1. Setting up your project with the necessary packages (such as the Oso Cloud Node SDK);

2. Defining policies and mapping roles to permissions;

3. Integrating authorization checks into route handlers or middleware;

4. Filtering data based on assigned roles; and

5. Testing and debugging the authorization logic.

Learn more about RBAC concepts, architecture, and best practices

We've written an Authorization Academy to help you get started with RBAC and other authorization topics. The guide is language and technology-agnostic and covers industry-standard authorization concepts. Learn:

a. How to architect your app for RBAC.

b. Common access control models like role-based access control (RBAC) and relationship-based access control (ReBAC) – like when to use them and how to implement them.

c. Where to enforce authorization at various layers in your app.

Conclusion

This guide has examined the implementation of Role-Based Access Control in a Node.js environment using Oso Cloud.

It outlined the process of defining roles and permissions, integrating authorization checks into Node code, and filtering data based on user roles. Additional resources, including the Node SDK documentation and the Authorization Academy, offer further insights into alternative access control models and architectural considerations for authorization.

You can implement ReBAC and ABAC as well in Node.js with Oso Cloud. Check out the following sources to learn how:

a. Relationship-Based Access Control (ReBAC) in Node.js with Oso Cloud

b. Implementing Attribute-based Access Control (ABAC) in Node.js with Oso Cloud

Join the community of thousands of developers in the Oso Slack (including many Node devs!) or feel free to set up a 1x1 with an Oso engineer to learn more about RBAC in Node, Oso Cloud, or just authorization in general. We'd love to talk about what you're working on and answer any questions you have.

ABAC, RBAC, ReBAC

When building applications with user permissions, Role-Based Access Control (RBAC) provides a structured way to define who can perform specific actions. Instead of scattering authorization logic throughout your codebase, RBAC organizes permissions into roles that determine access across different parts of an application. This guide walks through implementing RBAC in a Golang application using Oso Cloud.

It covers defining role-based rules, and filtering data based on user roles. Whether managing organizational roles, resource-specific roles, or cross-organization access, this approach ensures consistent authorization decisions across your system.

What is Role-Based Access Control (RBAC) in Golang?

Roles are a common way to simplify authorization logic for engineers and users. Authorization logic that’s based on roles is called “role-based access control.” It allows access control decisions to be based on predefined roles rather than individual users.

Authentication vs. Authorization

Before diving deeper into RBAC, it's important to distinguish between authentication and authorization:

a. Authentication verifies a user’s identity (e.g., logging in with a username and password, using OAuth, or biometric authentication). Most developers integrate third-party authentication providers (e.g., OAuth, OpenID Connect) rather than building authentication from scratch.

b. Authorization determines what a user is allowed to do after authentication. Unlike authentication, developers often implement authorization logic in-house, as it can be tightly coupled with business rules.

Why is RBAC needed?

Without RBAC, managing permissions in an application can become complex and error-prone, especially as the number of users and resources grows. Hardcoding permissions or managing them on a per-user basis leads to security risks and maintenance challenges. RBAC addresses these issues by:

  • Providing a structured way to group permissions under roles, reducing duplication.
  • Making it easier to modify access rules without changing application code.
  • Improving security by ensuring least privilege access.

For example, in a SaaS application, RBAC can help:

  • Restrict access to admin panels to only users with an "admin" role.
  • Control API endpoints so that only users with the "developer" role can modify application settings.
  • Protect databases, allowing only "database administrators" to execute ALTER statements.

Common RBAC concepts

Roles: A role is a way to group permissions. When a user is assigned a role, the user gets every permission that the role has. Some examples of roles are: `admin`, `editor`, `viewer`.

Permissions: A permission is an action that a user can take on a resource. For example, we might say that a user in an organization has permission to `read` repositories.

Policies: A policy defines how roles and permissions interact. For example, we might say an `admin` role has `delete` permission on all resources.

There are a number of variations on role-based access to allow for more flexible groupings of permissions, like:

a. Organizational roles

b. Cross-organization roles

c. Resource-specific roles

DIY RBAC vs. using an authorization service

When implementing RBAC in Golang, developers have two main options: building RBAC in-house (DIY) or using a third-party authorization service like Oso Cloud.

Challenges of DIY RBAC

Manually implementing RBAC often starts simple—storing roles and permissions in a database—but quickly becomes complex as requirements grow:

a. Scalability issues – Managing permissions across multiple microservices or tenants becomes difficult.

b. Policy changes require code updates – Hardcoded permissions lead to frequent deployments.

c. Difficult to extend – Adding fine-grained controls (e.g., resource-based permissions, hierarchical roles) increases complexity.

d. Security risks – Maintaining audit logs and preventing privilege escalation requires extra effort.

DIY vs. third-party authorization service

a. Setup: DIY requires custom role structures, databases, and enforcement logic. Authorization services provide prebuilt policies and integrations.

b. Scalability: Every time you add a new type of authorization logic to a DIY system, you have to build it yourself. Services handle things like hierarchy and multi-tenancy automatically.

c. Security: DIY needs manual audits and debugging. Services offer built-in validation (logging, auditing, etc.) and reduce misconfigurations.

d. Performance: DIY relies on database queries. Services use optimized, low-latency authorization checks.

e. Flexibility: DIY only supports what you build. Services support RBAC, ReBAC, and hybrid models.

Why use Oso Cloud?

Oso Cloud simplifies authorization by providing:

  • Declarative RBAC policies – Define roles and permissions without modifying application code.
  • Hybrid models – Supports RBAC + ReBAC + ABAC, enabling fine-grained, relationship and attribute based access.
  • Centralized management – Enforce consistent authorization across services and teams in microservices architectures.

For teams that need more than basic role checks—such as cross-organization roles or hierarchical permissions—Oso Cloud offers a scalable alternative to DIY solutions. Let's dive into it.

Oso Cloud is an authorization service for building RBAC in Go

a. Oso Cloud is fully-managed and deployed across multiple regions for low-latency and high availability

b. Oso Cloud comes out of the box with primitives for role-based access control (RBAC). It also includes built-ins for other access control models like relationship-based access control (ReBAC) or attribute-based access control (ABAC).

c. You provide Oso Cloud with the requisite authorization data, then your RBAC policy operates over that data to make authorization decisions at runtime.

d. Oso can provide yes/no authorization decisions, as well as filter lists of data.

Express RBAC in Go with Oso Cloud

To authorize whether a user has the role required to perform an action on a resource, call Oso in your controller.

// This will return `false` if the current user does not
// have access to the Repository that they're trying to read
user := NewValue("User", GetCurrentUser())
repo := NewValue("Repository", repoName)
oso.Authorize(user, "read", repo)


You’ll also write an Oso policy—that is, a set of rules—to implement role-based authorization. Here, we’ll show a policy for an app for source code hosting like GitHub or GitLab.

In this policy, users may or may not be able to read or make changes to a repository, depending on whether they’re members or owners. That means we need authorization based on users’ roles.

actor User {}

resource Organization {
 roles = ["owner"];
}

resource Repository {
 permissions = ["read", "push"];
 roles = ["contributor", "maintainer"];
 relations = { parent: Organization };

 # An actor has the "read" permission if they have the "contributor" role.
 "read" if "contributor";
 # An actor has the "push" permission if they have the "maintainer" role.
 "push" if "maintainer";

 # An actor has the "contributor" role if they have the "maintainer" role.
 "contributor" if "maintainer";

 # An actor has the "maintainer" role if they have the "owner" role on 
 # the "parent" Organization.
 "maintainer" if "owner" on "parent";
}


For a detailed guide on RBAC concepts, read our technology-agnostic Authorization Academy.

Oso isn’t limited to Role-based access control. It comes with primitives for other common access control models, like Relationship-based access control, or ReBAC. For a guide on other authorization patterns, take a look at our guide on authorization modeling covering roles, hierarchies, groups, and other patterns.

Filter data based on a user’s role

Your app needs to be able to return all the repos that a user can see based on their role and any other relevant criteria. To do this we can use the list method.

Here's that in the Go app again:

user := NewValue("User", GetCurrentUser())
repos, err := oso.List(user, "read", "Repository")


Learn how to implement authorization in Go

To implement authorization in Go using Oso, start by installing the Oso Go SDK (go get github.com/osohq/go-oso-cloud/v2).

Define roles and permissions using the declarative Polar language, such as granting "admin" users permission to edit documents. Once your policies are defined, load them into your Go app and enforce authorization checks to determine if a user can perform an action.

For detailed implementation steps, check out the Go SDK documentation.

Learn more about RBAC concepts, architecture, and best practices

We've written the Authorization Academy to help you get started with RBAC and other authorization topics. The guide is language and technology-agnostic and covers industry-standard authorization concepts. Learn:

a. How to architect your app for RBAC.

b. Common access control models like role-based access control (RBAC) and relationship-based access control (ReBAC) – like when to use them and how to implement them.

c. Where to enforce authorization at various layers in your app.

Conclusion

Join the community of thousands of developers in the Oso Slack (including many Gophers!) and feel free to set up a 1x1 with an Oso engineer to learn more about RBAC in Go, Oso Cloud, or just authorization in general. We'd love to talk about what you're working on and answer any questions you have.

ABAC, RBAC, ReBAC

When building applications with user permissions, Role-Based Access Control (RBAC) provides a structured way to define who can perform specific actions. Instead of scattering authorization logic throughout your codebase, RBAC organizes permissions into roles that determine access across different parts of an application. This guide walks through implementing RBAC in a Python application using Oso Cloud.

It covers defining role-based rules, and filtering data based on user roles. Whether managing organizational roles, resource-specific roles, or cross-organization access, this approach ensures consistent authorization decisions across your system.

What is Role-Based Access Control (RBAC) in Python?

Roles are a common way to simplify authorization logic for engineers and users. Authorization logic that’s based on roles is called “role-based access control.”

role is a way to group permissions. When a user is assigned a role, the user gets every permission that the role has.

permission is an action that a user can take on a resource. For example, we might say that a user in an organization has permission to read repositories.

There are a number of variations on role-based access to allow for more flexible groupings of permissions, like:

a. Organizational roles

b. Cross-organization roles

c. Resource-specific roles

To implement RBAC in Python, you can use either built-in logic or third-party libraries

Built-in logic approaches:

a. Using Python classes – Create a Role class and a User class to manage roles and permissions.

b. RBAC with JSON for configurable roles – Store roles and permissions in a JSON file, allowing dynamic updates without modifying the code.

c. RBAC with a SQL database – Store roles and permissions in an SQL database for easy querying.

d. RBAC with environment variables – Configure permissions dynamically in serverless functions or cloud environments.

Third-party libraries:

a. Flask-Principal – For managing RBAC in Flask applications.

b. Oso – A declarative authorization framework.

Why use RBAC in Python?

RBAC in Python centralizes permission management by assigning roles to users instead of handling individual permissions. This approach reduces complexity and ensures consistent access control.

Key benefits of implementing RBAC in Python:

a. Groups permissions into roles, simplifying authorization logic.

b. Enforces the principle of least privilege by restricting access based on roles.

c. Supports large user bases by structuring access control at different levels.

d. Integrates with databases and frameworks like Django, FastAPI, and Flask.

e. Works alongside models like ABAC or ReBAC for fine-grained access control.

f. Helps meet compliance requirements for standards like GDPR and HIPAA.

Python’s libraries and frameworks provide tools for implementing RBAC with structured policies and data management.

Oso Cloud is an authorization service for building RBAC in Python

Oso’s Python SDK provides tools for defining and enforcing RBAC policies. It supports declarative policy definitions and integrates with existing applications. Oso can be used for new implementations or to enhance existing authorization systems.

For detailed implementation guidance, refer to the Python SDK documentation.

4 reasons to build RBAC in Python with Oso:

1. Oso Cloud is fully managed and deployed across multiple regions for low latency and high availability.

2. Oso Cloud comes out of the box with primitives for role-based access control (RBAC). It also includes built-ins for other access control models like relationship-based access control (ReBAC) or attribute-based access control (ABAC).

3. You provide Oso Cloud with the requisite authorization data, then your RBAC policy operates that data to make authorization decisions at runtime.

4. Oso can provide yes/no authorization decisions, as well as filter lists of data.

Express RBAC in Python with Oso Cloud

To authorize whether a user has the role required to perform an action on a resource, call Oso in your controller.

# This will return `False` if the current user does not
# have access to the Repository that they're trying to read
user = Value("User", User.get_current_user())
repo = Value("Repository", repo_name)
oso.authorize(user, "read", repo)


You’ll also write an Oso policy—that is, a set of rules—to implement role-based authorization. Here, we’ll show a policy for an app for source code hosting like GitHub or GitLab.

In this policy, users may or may not be able to read or make changes to a repository, depending on whether they’re members or owners. That means we need authorization based on users’ roles.

actor User {}

resource Organization {
 roles = ["owner"];
}

resource Repository {
 permissions = ["read", "push"];
 roles = ["contributor", "maintainer"];
 relations = { parent: Organization };

 # An actor has the "read" permission if they have the "contributor" role.
 "read" if "contributor";
 # An actor has the "push" permission if they have the "maintainer" role.
 "push" if "maintainer";

 # An actor has the "contributor" role if they have the "maintainer" role.
 "contributor" if "maintainer";

 # An actor has the "maintainer" role if they have the "owner" role on the 
 # "parent" Organization.
 "maintainer" if "owner" on "parent";
}


To learn how to model RBAC with Oso, take a look at our RBAC modeling guide.

For a guide on other authorization models, such as ReBAC or ABAC, check out our authorization modeling guide covering roles, hierarchies, groups, and other patterns.

Filter data based on a user’s role

Your app needs to be able to return all the repos that a user can see based on their role and any other relevant criteria. To do this we can use the list method.

Here's that in the Python app again:

user = Value("User", User.get_current_user())
repos = oso.list(user, "read", "Repository")


Learn more about RBAC concepts, architecture, and best practices

We've written Authorization Academy to help you get started with RBAC and other authorization topics. The guide is language and technology-agnostic and covers industry-standard authorization concepts. Learn:

a. How to architect your app for RBAC.

b. Common access control models like role-based access control (RBAC) and relationship-based access control (ReBAC) – when to use them and how to implement them.

c. Where to enforce authorization at various layers in your app.


Python RBAC FAQ

What are the challenges of implementing RBAC in Python?

Building an RBAC system from scratch in Python involves:

a. Defining and managing roles and permissions.

b. Scaling as the number of users increases.

c. Integrating with authentication systems and APIs.

Using an authorization software like Oso addresses these challenges by providing a pre-built system that integrates into Python applications.

Should you build your own RBAC solution or use an existing framework like Oso?

Building an RBAC system in-house requires significant time and maintenance. Tools like Oso reduce engineering effort by providing a ready-to-use authorization framework. You can read more on this topic in our blog post "Build or Buy".

Can Oso be used with popular Python frameworks like Flask or FastAPI?

Yes, Oso integrates with Flask and FastAPI using its Python SDK, allowing role-based permissions to be enforced within existing application structures. The Oso SDK provides web framework middleware for oso-cloud, the Oso Cloud Client for Python. You can install it by running:

pip install oso-sdk

How do I get started with RBAC in Python using Oso?

1. Install the Oso library: pip install oso-cloud

2. Define roles and permissions in a policy file using Oso’s declarative syntax.

3. Embed Oso in your application to enforce these permissions.

For detailed implementation steps, refer to Oso’s official documentation.

Conclusion

Join the community of thousands of developers in the Oso Slack (including many Pythonistas!) and feel free to set up a 1x1 with an Oso engineer to learn more about RBAC in Python, Oso Cloud, or just authorization in general. We'd love to talk about what you're working on and answer any questions you have.

Authorization done right