Skip to main content

ABAC Design Patterns

Below you'd find common authorization design-patterns implemented in ABAC, and guides on how to implement them.

Ownership

Ownership is one of the more common authorization patterns - in which a user can perform actions only on resources they own. There are three easy ways (in addition to writing policy directly as code) to implement ownership in Permit.io - all are equally valid and it's mostly a matter of taste.

  • Ownership via tenants

    Ownership is defined by having the user and resource belong to the same tenant. While implemented as an ABAC policy behind the scenes - you don't need to actually do so yourself - and you can enjoy the simplified API which treats tenants as a first class citizen.

    Read here on creating and checking permissions based on tenancy.

  • Ownership via list on resource

    The resource has an attribute listing all the user ids that own it. Use a resource-set to check against the list.

    It would look something like this:

    And the permission check (assuming loading the attribute as part of the check) would look something like this:

    const permitted = await permit.check(userKey, "read", {
    type: "file", // The resource name
    attributes: {
    owners: [
    "d08c85a349994aeb89a3f02c08bdb340", // user-1
    "48fb889360604253a5189580b48694cf", // user-2
    ],
    },
    });
  • Ownership via list on user profile

    The user profile has an attribute listing all the resource ids that it owns. Use a user-set to check against the list.

Belonging to a group

Groups are a classic ReBAC concept; but ReBAC is a subset of ABAC, and we can often use it to implement group affiliation. The group can be represented as a resource by itself, a tenant, or an attribute of users or resources.

  • Group by tenants

    You can use tenants as groups - having several tenants together form the actual higher level tenant. Those can then be grouped by tenant attributes.

  • Group by attributes

    You can assign users and resources to a group using an attribute like group_id or group_name - Implementation here is very similar to the ownership design pattern.

info

Once we add ReBAC and groups natively to Permit.io - those would be the default recommended way to implement groups.

Combining your own OPA Rego code with Permit.io generated ABAC code

Permit.io allows you to extend its ABAC capabilities by writing custom Rego code. This enables you to create dynamic attributes that can be used in your ABAC policies.

How it works

  1. Write custom Rego code that generates attributes
  2. Use these attributes in your ABAC policies just like static attributes
  3. The custom code runs alongside Permit.io's generated ABAC code

Example: Creating a Dynamic Intersection Attribute

Let's say you want to check if there's an intersection between user and resource attributes. Instead of using static values, you can create a dynamic attribute using Rego.

Add this code to your gitops repository in the custom/ directory:

package permit.custom
import future.keywords.in

# Default value if no conditions are met
default custom_user_attributes["has_intersection"] = false

# Set to true if there's an intersection between user and resource attributes
custom_user_attributes["has_intersection"] := true {
# Convert attributes to sets for intersection testing
user_attr_set = {x | some x in data.users[input.user.key].attributes["attr_in_user"]}
res_attr_set = {x | some x in input.resource.attributes["attr_in_resource"]}
# Check if there's at least one common value
count(user_attr_set & res_attr_set) > 0
}
Important Requirements
  • The package name must be permit.custom
  • The rule name must be custom_user_attributes
  • These are the only requirements - you can write any Rego logic you need

Using the Custom Attribute

Once defined, you can use this attribute in your ABAC policies just like any other attribute:

const permitted = await permit.check(userKey, "read", {
type: "document",
attributes: {
attr_in_resource: ["tag1", "tag2"],
},
});

Testing Your Custom Code

  1. Using the OPA Playground

  2. Testing with Local PDP

    docker run -it -p 7767:7000 -p 8181:8181 -e PDP_API_KEY=permit_key_XXXXX permitio/pdp-v2:latest

    Then you can:

    • Send POST requests to http://localhost:8181/v1/data/permit with your input data
    • Get raw outputs including policy debug data
    • Update custom policies directly at http://localhost:8181/v1/policies/custom/example.rego
Development Workflow
  1. Start with the OPA Playground for quick iterations
  2. Move to local PDP for more realistic testing
  3. Once satisfied, commit to your gitops repository