ABAC Design Patterns
Implement common authorization patterns with attribute-based access control (ABAC) in Permit.io: ownership, group membership, comparisons between two attributes, several condition groups in one set, and dynamic attributes computed with custom Rego code. This page is for developers who know how to create user sets and resource sets, and want a recommended way to model a specific rule. To create user sets and resource sets, see Building your first ABAC policy.
Prerequisites
- A Permit.io account with a project and an environment. See the Quickstart.
- A resource with the actions you want to grant. See Create the bicycle resource.
- A container policy decision point (PDP) running at
http://localhost:7766for the checks on this page. The Cloud PDP doesn't evaluate ABAC policies. See Cloud PDP capabilities.
Every JavaScript example on this page uses this client, and userKey is the key of the user who makes the request. Replace <YOUR_API_KEY> with your environment API key:
import { Permit } from "permitio";
const permit = new Permit({
token: "<YOUR_API_KEY>",
pdp: "http://localhost:7766",
});
Ownership
With ownership, a user can perform actions only on the resources they own. You can implement ownership in Permit in three ways with the Policy Editor, or write the policy as code. The three ways are equally valid:
| Approach | Where ownership is stored | Use when |
|---|---|---|
| Ownership via tenants | The tenant of the user and of the resource | Each owner already maps to a tenant |
| Ownership via a list on the resource | A resource attribute that lists the owner keys | Your application knows a resource's owners when it runs the check |
| Ownership via a list on the user profile | A user attribute that lists the resource keys the user owns | Your application stores ownership on the user |
To model ownership with relationship-based access control (ReBAC) instead, see Model ownership.
Ownership via tenants
A user owns a resource when the user and the resource belong to the same tenant. This pattern needs no ABAC condition set: Permit scopes every role assignment to a tenant, so a role-based access control (RBAC) role grants its permissions only inside the tenant it was assigned in. Pass the tenant in permit.check(), and the check allows the action only when the user holds a role that permits the action in that tenant.
To learn how tenants isolate users and resources, see Multi-tenant authorization. To assign a role in one tenant, see Create a user and assign the Admin role.
Ownership via list on resource
The resource has an attribute that lists the keys of the users who own it. A resource set checks whether the list contains the key of the user who makes the request.
- Add an array attribute to the resource, for example
owner. - Create a resource set with the condition
resource.ownerarray contains (ref)user.key. - Grant the resource set its actions in the Policy Editor.

To check permissions, pass the owner list as a resource attribute in permit.check(). In the following example, userKey is the key of the user who makes the request:
const permitted = await permit.check(userKey, "read", {
type: "file", // The resource name
attributes: {
owner: [
"d08c85a349994aeb89a3f02c08bdb340", // user-1
"48fb889360604253a5189580b48694cf", // user-2
],
},
});
The check returns true when userKey is one of the keys in owner.
Ownership via list on user profile
The user has an attribute that lists the keys of the resources the user owns. A user set checks whether that list contains the key of the requested resource, which your application passes as a resource attribute.
- Add an array attribute to the user, for example
owned_files, and store the keys of the resources the user owns. - Add an attribute to the resource, for example
id, that your application fills with the key of the resource it checks. - Create a user set with the condition
user.owned_filesarray contains (ref)resource.id. - Grant the user set its actions in the Policy Editor.
To check permissions, pass both attributes in permit.check():
const permitted = await permit.check(
{
key: "john@permit.io",
attributes: { owned_files: ["readme", "budget"] },
},
"read",
{
type: "file",
attributes: { id: "readme" },
}
);
The check returns true, because owned_files contains readme. With id: "roadmap", the same check returns false.
A user set matches a user in every tenant. To keep this pattern inside one tenant, add the conditions described in the tenant boundary warning of Create the Full-time Stanford Student user set.
Belonging to a group
Permit supports groups natively with ReBAC. To assign roles to a group instead of to each member, use the Groups API or read the ReBAC overview.
You can also represent a group with ABAC, as a tenant or as an attribute of users and resources.
Group by tenants
Use tenants as groups, so several tenants together form a higher-level group. Group the tenants with tenant attributes.
Group by attributes
Assign users and resources to a group with an attribute such as group_id or group_name. The implementation follows the ownership via a list on the resource pattern: compare the group attribute of the resource with the group attribute of the user.
Compare a resource attribute with a user attribute
A condition can compare an attribute with another attribute instead of with a fixed value. The Policy Editor marks these operators with (ref), and the policy decision point (PDP) resolves the referenced attribute when it evaluates the check.
For example, to allow an action only when the risk level of the resource is at or below the risk level the user is cleared for:
- Add a
riskattribute of type Number to the resource, and ariskattribute of type Number to the user. - Create a resource set on that resource type with the condition
resource.riskless-than-equals (ref)user.risk. - Grant the resource set its actions in the Policy Editor.

Give both attributes a value of the type the operator accepts. Comparison operators lists the operand type of each operator, and Compare two attributes with a reference lists the operators that accept a reference.
Combine several condition groups
A condition set can hold several condition groups. Conditions inside a group are joined by the operator you select on each row (and or or), and the groups are joined by the operator you select between them.
- In the user set or resource set form, add the conditions of the first group, and select
andororon each row after the first. - Click Add Condition Group to add another group.
- Select the operator between the groups.

For the JSON that the API stores for these groups, see Build ABAC conditions.
Compute dynamic attributes with custom Rego code
Add custom Open Policy Agent (OPA) Rego code to compute dynamic attributes that your ABAC policies use like any other attribute.
How dynamic attributes work
- You write custom Rego code that generates attributes.
- You use the generated attributes in user set and resource set conditions, as you use stored attributes.
- The policy decision point (PDP) runs your custom code with the ABAC code that Permit generates.
Example: Creating a dynamic intersection attribute
The following example computes a has_intersection user attribute that is true when a list attribute on the user and a list attribute on the resource share at least one value.
Add the code to the custom/ directory of your GitOps repository:
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
}
The code reads attr_in_user from the stored attributes of the user, and attr_in_resource from the resource attributes in the check input.
- The package name must be
permit.custom. - The rule name must be
custom_user_attributes. - There are no other requirements. The rule body can contain any Rego logic.
Use the has_intersection attribute in a condition
Use user.has_intersection in a user set condition, and pass the resource attribute in the check:
const permitted = await permit.check(userKey, "read", {
type: "document",
attributes: {
attr_in_resource: ["tag1", "tag2"],
},
});
The check returns true when the user's attr_in_user list contains tag1 or tag2, and the user set with the has_intersection condition has read permission on document.
Test the custom Rego code
-
Test in the OPA Playground. Open this example in the OPA Playground to iterate on the Rego logic without a PDP.
-
Test with a local PDP. Run a PDP container. Replace
permit_key_XXXXXwith your environment API key. The command maps the PDP API to port7767on your machine, and the OPA API to port8181.docker run -it -p 7767:7000 -p 8181:8181 -e PDP_API_KEY=permit_key_XXXXX permitio/pdp-v2:latestWith the PDP running, you can:
- Send POST requests with your input data to
http://localhost:8181/v1/data/permit, and read the raw output, including policy debug data. - Update a custom policy directly at
http://localhost:8181/v1/policies/custom/example.rego.
- Send POST requests with your input data to
- Iterate on the Rego logic in the OPA Playground.
- Test with a local PDP and your real policy data.
- Commit the code to your GitOps repository.
Next steps
- Defining attributes: store attribute values or pass them in the check.
- ABAC condition operators: every operator a condition can use, including the reference operands.
- Write custom policies: add your own Rego code to the policy repository.
- Model ownership: compare ABAC and ReBAC approaches to resource ownership.