Common Patterns
Authorization patterns for RBAC, ABAC, and more
This tutorial covers common authorization patterns you can implement in Denied Platform. Each pattern includes complete Rego code examples and test cases you can try in the Playground.
Role-Based Access Control (RBAC)
RBAC assigns permissions based on user roles. It's simple to understand and implement, making it the most common authorization model.
Simple Role Check
The simplest RBAC checks if the user has a specific role:
input.subject.properties.role == "admin"Test Case:
{
"subject": { "type": "user", "id": "1", "properties": { "role": "admin" } },
"action": { "name": "delete" },
"resource": { "type": "user", "id": "2", "properties": { "type": "user" } }
}
// Expected: allow = trueRole-Permission Mapping
Map roles to specific permissions:
# Define what each role can do
role_permissions := {
"admin": ["create", "read", "update", "delete"],
"editor": ["create", "read", "update"],
"viewer": ["read"]
}
# Get the user's role
role := input.subject.properties.role
# Get permissions for that role
permissions := role_permissions[role]
# Check if the requested action is in the permissions
input.action.name == permissions[_]Test Cases:
// Admin deleting - should allow
{ "subject": { "properties": { "role": "admin" } }, "action": { "name": "delete" }, "resource": { "properties": {} } }
// Viewer reading - should allow
{ "subject": { "properties": { "role": "viewer" } }, "action": { "name": "read" }, "resource": { "properties": {} } }
// Viewer deleting - should deny
{ "subject": { "properties": { "role": "viewer" } }, "action": { "name": "delete" }, "resource": { "properties": {} } }Multiple Roles
Users can have multiple roles. Allow if any role grants permission:
role_permissions := {
"billing": ["view_invoices", "manage_payments"],
"support": ["view_tickets", "respond_tickets"],
"admin": ["view_invoices", "manage_payments", "view_tickets", "respond_tickets", "manage_users"]
}
# Check each role the user has
some role in input.subject.properties.roles
# Get permissions for that role
permissions := role_permissions[role]
# Check if action is allowed
input.action.name == permissions[_]Test Case:
{
"subject": { "properties": { "roles": ["billing", "support"] } },
"action": { "name": "view_invoices" },
"resource": { "properties": {} }
}
// Expected: allow = true (billing role grants this)Hierarchical Roles
Implement role hierarchies where higher roles inherit lower role permissions:
# Define role hierarchy (higher includes lower)
role_hierarchy := {
"admin": ["manager", "employee", "guest"],
"manager": ["employee", "guest"],
"employee": ["guest"],
"guest": []
}
# Role permissions
role_permissions := {
"guest": ["read_public"],
"employee": ["read_internal", "create_draft"],
"manager": ["approve", "assign"],
"admin": ["delete", "configure"]
}
# Get user's role and inherited roles
user_role := input.subject.properties.role
inherited_roles := role_hierarchy[user_role]
# Collect all permissions from user's role and inherited roles
user_permissions := role_permissions[user_role]
inherited_permissions := [perm |
some r in inherited_roles
some perm in role_permissions[r]
]
# Check if action is in user's permissions or inherited permissions
input.action.name == user_permissions[_]Or check inherited permissions:
input.action.name == inherited_permissions[_]Test Case:
{
"subject": { "properties": { "role": "manager" } },
"action": { "name": "read_internal" },
"resource": { "properties": {} }
}
// Expected: allow = true (inherited from employee role)Attribute-Based Access Control (ABAC)
ABAC makes decisions based on properties of users, resources, and context. It's more flexible than RBAC but can be more complex.
Simple Attribute Match
Match user and resource attributes/properties:
# Users can access resources in their department
input.subject.properties.department == input.resource.properties.departmentTest Case:
{
"subject": { "properties": { "user_id": "user-1", "department": "engineering" } },
"action": { "name": "read" },
"resource": { "properties": { "id": "doc-1", "department": "engineering" } }
}
// Expected: allow = trueMulti-Attribute Policies
Combine multiple properties checks:
# User is in the same department
input.subject.properties.department == input.resource.properties.department
# User has sufficient clearance
input.subject.properties.clearance_level >= input.resource.properties.required_clearance
# User's account is active
input.subject.properties.status == "active"Test Case:
{
"subject": {
"properties": {
"department": "research",
"clearance_level": 3,
"status": "active"
}
},
"action": { "name": "read" },
"resource": {
"properties": {
"department": "research",
"required_clearance": 2
}
}
}
// Expected: allow = trueLocation-Based Access
Restrict access based on geographic or network properties:
allowed_countries := ["US", "CA", "GB"]
# User is in an allowed country
input.context.country == allowed_countries[_]
# Not using a VPN
input.context.vpn == false
# On corporate network
input.context.corporate_network == trueAlternative policy for verified devices from anywhere:
input.context.device_verified == true
input.subject.properties.mfa_enabled == trueData Classification
Control access based on data sensitivity:
# Map clearance levels to data classifications they can access
clearance_access := {
"top_secret": ["top_secret", "secret", "confidential", "internal", "public"],
"secret": ["secret", "confidential", "internal", "public"],
"confidential": ["confidential", "internal", "public"],
"internal": ["internal", "public"],
"public": ["public"]
}
user_clearance := input.subject.properties.clearance
resource_classification := input.resource.properties.classification
# Get what classifications this clearance can access
accessible := clearance_access[user_clearance]
# Check if resource classification is accessible
resource_classification == accessible[_]Resource Ownership
One of the most common patterns — users can access their own resources.
Basic Ownership
input.resource.properties.owner == input.subject.properties.user_idOwnership with Action Restrictions
Owners might have full access, while others have limited access.
Policy 1: Owner Full Access
input.resource.properties.owner == input.subject.properties.user_idPolicy 2: Public Read Access (for non-owners)
input.resource.properties.owner != input.subject.properties.user_id
input.action.name == "read"
input.resource.properties.visibility == "public"Shared Resources
Handle resources shared with specific users.
Policy 1: Owner Full Access
input.resource.properties.owner == input.subject.properties.user_idPolicy 2: Shared Users Read Access
shared_users := input.resource.properties.shared_with
input.subject.properties.user_id == shared_users[_]
input.action.name == "read"Policy 3: Editors Read/Update Access
editors := input.resource.properties.editors
input.subject.properties.user_id == editors[_]
allowed_actions := ["read", "update"]
input.action.name == allowed_actions[_]Test Case:
{
"subject": { "properties": { "user_id": "user-2" } },
"action": { "name": "read" },
"resource": {
"properties": {
"owner": "user-1",
"shared_with": ["user-2", "user-3"]
}
}
}
// Expected: allow = trueTeam and Organization Access
Team Membership
Allow access to team resources:
# User is a member of the team that owns the resource
team_ids := input.subject.properties.team_ids
input.resource.properties.team_id == team_ids[_]Organization Isolation (Multi-Tenancy)
Ensure users can only access resources in their organization:
# CRITICAL: Always check organization match first
input.subject.properties.org_id == input.resource.properties.org_id
# Then apply permission check (e.g., admin role)
input.subject.properties.role == "admin"Or for owner access within organization:
input.subject.properties.org_id == input.resource.properties.org_id
input.resource.properties.owner == input.subject.properties.user_idAlways enforce tenant isolation as the first check in multi-tenant systems. A bug in other permission logic shouldn't allow cross-tenant access.
Team Hierarchy
Managers can access their team's resources.
Policy 1: Direct Team Member Access
input.resource.properties.team_id == input.subject.properties.team_id
allowed_actions := ["read", "update"]
input.action.name == allowed_actions[_]Policy 2: Manager Access to Subordinate Teams
managed_teams := input.subject.properties.managed_teams
input.resource.properties.team_id == managed_teams[_]Time-Based Access
Business Hours Only
# Get current hour (0-23)
hour := time.clock(time.now_ns())[0]
# Allow only during business hours (9 AM - 6 PM)
hour >= 9
hour < 18
# Also check it's a weekday
day := time.weekday(time.now_ns())
day != "Saturday"
day != "Sunday"Temporary Access
Check if access grant is still valid:
# Check if user has temporary access
grant := input.subject.properties.temporary_access[_]
# Grant is for this resource
grant.resource_id == input.resource.properties.id
# Grant hasn't expired
grant.expires_at > time.now_ns() / 1000000000 # Convert to secondsScheduled Maintenance Windows
Policy 1: Normal Access (non-maintenance)
# Check not in maintenance window
hour := time.clock(time.now_ns())[0]
day := time.weekday(time.now_ns())
not (day == "Sunday")
# Allow standard roles
allowed_roles := ["admin", "editor", "viewer"]
input.subject.properties.role == allowed_roles[_]Policy 2: Admin-Only During Maintenance
# Check in maintenance window (Sundays 2-6 AM)
hour := time.clock(time.now_ns())[0]
day := time.weekday(time.now_ns())
day == "Sunday"
hour >= 2
hour < 6
# Only admins allowed
input.subject.properties.role == "admin"Combining Patterns
Real-world policies often combine multiple patterns:
RBAC + Ownership + ABAC
Policy 1: Admin Access (within org)
input.subject.properties.org_id == input.resource.properties.org_id
input.subject.properties.role == "admin"Policy 2: Owner Full Access (within org)
input.subject.properties.org_id == input.resource.properties.org_id
input.resource.properties.owner == input.subject.properties.user_idPolicy 3: Department Read Access (non-confidential)
input.subject.properties.org_id == input.resource.properties.org_id
input.subject.properties.department == input.resource.properties.department
input.action.name == "read"
input.resource.properties.classification != "confidential"Policy 4: Public Read Access (within org)
input.subject.properties.org_id == input.resource.properties.org_id
input.resource.properties.visibility == "public"
input.action.name == "read"AI Agent Authorization
Special pattern for authorizing AI agents:
Policy 1: Agent Passport Validation
# Verify agent has passport
input.subject.properties.type == "agent"
passport := input.subject.properties.passport
# Passport is not expired
passport.expires_at > time.now_ns() / 1000000000
# Check agent's allowed actions
input.action.name == passport.allowed_actions[_]
# Check resource type is permitted
input.resource.properties.type == passport.allowed_resource_types[_]
# Data sensitivity check
input.resource.properties.sensitivity <= passport.max_sensitivity_levelPolicy 2: Human User RBAC
input.subject.properties.type == "user"
allowed_roles := ["admin", "editor"]
input.subject.properties.role == allowed_roles[_]Deny Rules
Sometimes it's clearer to explicitly deny certain conditions. In Denied Platform, create policies with the "Deny" effect:
Deny Suspended Users
Deny Policy: Suspended Status
input.subject.properties.status == "suspended"Deny Policy: Banned Status
input.subject.properties.status == "banned"Note: Deny policies automatically take precedence over allow policies.
Rate Limiting
Deny Policy: Hourly Rate Limit
# Deny if user has exceeded rate limit
input.context.requests_this_hour > 1000Deny Policy: Resource Access Limit
# Deny if specific resource is being accessed too frequently
input.context.resource_access_count > 100Testing Your Patterns
For each pattern, test:
- Positive cases — Should allow
- Negative cases — Should deny
- Edge cases — Missing fields, boundary values
- Security cases — Attempt to bypass restrictions
Example test matrix for RBAC + Ownership:
| Subject | Resource | Action | Expected |
|---|---|---|---|
| admin, same org | any | any | Allow |
| owner, same org | owned resource | any | Allow |
| viewer, same org | public resource | read | Allow |
| viewer, same org | private resource | read | Deny |
| any, different org | any | any | Deny |
Related
- Rego Basics — Language fundamentals
- Testing Policies — Validate in the Playground
- Policies Guide — Manage policies in Denied