Denied Docs

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 = true

Role-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.department

Test Case:

{
  "subject": { "properties": { "user_id": "user-1", "department": "engineering" } },
  "action": { "name": "read" },
  "resource": { "properties": { "id": "doc-1", "department": "engineering" } }
}
// Expected: allow = true

Multi-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 = true

Location-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 == true

Alternative policy for verified devices from anywhere:

input.context.device_verified == true
input.subject.properties.mfa_enabled == true

Data 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_id

Ownership 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_id

Policy 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_id

Policy 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 = true

Team 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_id

Always 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 seconds

Scheduled 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_id

Policy 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_level

Policy 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 > 1000

Deny Policy: Resource Access Limit

# Deny if specific resource is being accessed too frequently
input.context.resource_access_count > 100

Testing Your Patterns

For each pattern, test:

  1. Positive cases — Should allow
  2. Negative cases — Should deny
  3. Edge cases — Missing fields, boundary values
  4. Security cases — Attempt to bypass restrictions

Example test matrix for RBAC + Ownership:

SubjectResourceActionExpected
admin, same organyanyAllow
owner, same orgowned resourceanyAllow
viewer, same orgpublic resourcereadAllow
viewer, same orgprivate resourcereadDeny
any, different organyanyDeny

On this page