Loading content...
Consider this authorization requirement: "Users can access patient records only if they are the patient's assigned doctor, during their shift hours, from an approved hospital network, and the patient hasn't restricted access to their records."
Try expressing this with RBAC. You can't—not elegantly, anyway. You'd need roles for every doctor-patient pair, combined with every shift pattern, from every network location. The role explosion would be immediate and catastrophic.
Now consider how you'd actually express this in natural language: it's a combination of attributes—the user's role (doctor), their relationship to the patient (assigned), the current time (during shift), the network location (approved), and the resource state (not restricted). Each attribute contributes to the authorization decision.
This is the essence of Attribute-Based Access Control (ABAC). Instead of pre-defining static roles with fixed permissions, ABAC evaluates policies against dynamic attributes of subjects, resources, actions, and environment—at the moment access is requested. ABAC is the most expressive and flexible authorization model, capable of representing virtually any access control policy that can be described in human language.
By the end of this page, you will understand the ABAC model and its components, know how to design effective ABAC policies, implement attribute-based authorization with modern policy engines, understand the operational challenges and performance implications, and make informed decisions about when ABAC is the right choice.
ABAC was formalized by NIST in Special Publication 800-162 (2014) as a logical evolution beyond RBAC. Where RBAC uses roles as the primary abstraction, ABAC uses attributes—properties or characteristics of entities involved in an access request.
The ABAC Authorization Model:
An ABAC authorization request is evaluated by a policy that examines attributes from four categories:
The policy engine evaluates these attributes against defined rules and returns an authorization decision: Permit, Deny, or Not Applicable.
Formal ABAC Definition:
Decision = Policy(Subject_Attributes, Resource_Attributes, Action_Attributes, Environment_Attributes)
Where:
Decision ∈ {Permit, Deny, NotApplicable, Indeterminate}
Policy = Set of Rules with combining algorithms
Attributes = Key-value pairs describing entities
Example Attributes:
{
"subject": {
"id": "user-alice-123",
"role": "doctor",
"department": "cardiology",
"clearance_level": 3,
"employment_status": "active",
"certifications": ["HIPAA", "PHI_Handler"]
},
"resource": {
"id": "patient-record-789",
"type": "medical_record",
"classification": "PHI",
"patient_id": "patient-789",
"assigned_doctors": ["user-alice-123", "user-bob-456"],
"restricted": false
},
"action": {
"name": "read",
"method": "GET",
"fields_requested": ["diagnosis", "medications"]
},
"environment": {
"time": "2024-01-08T14:30:00Z",
"ip_address": "192.168.1.50",
"network": "hospital_internal",
"device_type": "workstation",
"risk_score": 0.2
}
}
ABAC is a superset of RBAC. Any RBAC policy can be expressed in ABAC by treating 'role' as a subject attribute: 'if subject.role == admin then permit'. This means ABAC can handle everything RBAC can, plus much more—but at the cost of additional complexity.
The XACML (eXtensible Access Control Markup Language) standard defines a reference architecture for ABAC systems. While not all implementations follow XACML exactly, understanding this architecture provides a framework for any ABAC system.
Core ABAC Components:
ABAC Request Flow:
┌────────────────────────────────────────────────────────────────┐
│ ABAC Request Flow │
└────────────────────────────────────────────────────────────────┘
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client │────────▶│ PEP │────────▶│ PDP │
└─────────┘ └────┬────┘ └────┬────┘
│ │
│ Request │
│◀──────────────────│
│ │ Policy Eval
│ ▼
│ ┌─────────┐
│ │ PRP │ (Policies)
│ └─────────┘
│ │
│ ┌─────────┐
│◀─────────────│ PIP │ (Attributes)
│ └─────────┘
│
│ Decision
▼
┌─────────┐
│ Resource│
└─────────┘
Step-by-Step Flow:
The PDP can be deployed centrally (all services call one PDP), as a sidecar (each service has its own PDP instance), or embedded within applications (library-based PDP). Trade-offs involve latency, consistency, and operational complexity.
ABAC policies can be expressed in various languages, from general-purpose config formats to specialized policy DSLs. The choice of policy language significantly impacts policy expressiveness, maintainability, and runtime performance.
Modern Policy Languages:
| Language | Used By | Style | Strengths |
|---|---|---|---|
| Rego | Open Policy Agent (OPA) | Datalog-inspired | Powerful queries, wide adoption, Kubernetes native |
| Cedar | AWS Verified Permissions | Declarative | Formally verified, readable syntax, fast evaluation |
| Polar | Oso | Logic programming | Application-embedded, relationship-aware |
| XACML | Enterprise IAM | XML-based | Standards compliant, expressive, verbose |
| Casbin | Casbin library | PERM model | Simple, multi-language, configurable |
Rego (Open Policy Agent) Example:
package medical.records
import future.keywords.if
import future.keywords.in
default allow := false
# Doctors can read records of assigned patients during shift hours
allow if {
input.action.name == "read"
input.subject.role == "doctor"
input.subject.id in input.resource.assigned_doctors
input.resource.restricted == false
is_during_shift(input.environment.time, input.subject.shift_schedule)
is_approved_network(input.environment.network)
}
# Patients can always read their own records
allow if {
input.action.name == "read"
input.subject.role == "patient"
input.subject.patient_id == input.resource.patient_id
}
# Emergency override with audit logging
allow if {
input.action.name == "read"
input.subject.role in ["doctor", "nurse"]
input.environment.emergency_mode == true
# Policy also triggers audit obligation
}
# Helper functions
is_during_shift(current_time, schedule) if {
time.parse_rfc3339_ns(current_time) >= schedule.start
time.parse_rfc3339_ns(current_time) <= schedule.end
}
is_approved_network(network) if {
network in ["hospital_internal", "vpn_trusted", "emergency_mobile"]
}
Cedar (AWS) Example:
// Doctors can read records of their assigned patients
permit (
principal is MedicalStaff,
action == Action::"read",
resource is PatientRecord
) when {
principal.role == "doctor" &&
principal in resource.assignedDoctors &&
!resource.restricted &&
context.network in ["hospital_internal", "vpn_trusted"]
};
// Patients can read their own records
permit (
principal is Patient,
action == Action::"read",
resource is PatientRecord
) when {
principal.patientId == resource.patientId
};
// Default deny all other access
forbid (
principal,
action,
resource
);
Policy Language Selection Criteria:
For new projects, Open Policy Agent (with Rego) offers the widest ecosystem and Kubernetes integration. AWS Cedar provides formally verified correctness guarantees. Both are battle-tested and well-documented. Avoid building custom policy languages.
ABAC's power comes from attributes, but managing attributes at scale is often the hardest part of an ABAC implementation. Attributes must be defined, sourced, kept current, and made available for policy evaluation—all with minimal latency impact.
Attribute Taxonomy:
| Category | Examples | Sources | Volatility |
|---|---|---|---|
| Subject/Identity | user_id, roles, department, clearance, certifications | IdP, HR systems, directory | Low (changes with role changes) |
| Resource/Object | owner, classification, sensitivity, state, created_date | Application database, metadata store | Medium (changes with resource lifecycle) |
| Action/Operation | method, operation_type, requested_fields, parameters | Request context, API definition | Per-request (static within request) |
| Environment/Context | time, IP, device, location, risk_score, session_age | Runtime context, security systems | High (changes continuously) |
Attribute Sourcing Strategies:
1. Request-Embedded Attributes: Include attributes in the request itself (e.g., JWT claims). Fast but limited to what's known at token issuance.
// Attributes embedded in JWT
{
"sub": "user-123",
"roles": ["doctor"],
"department": "cardiology",
"clearance": 3
}
2. PIP Lookups (Real-Time): Query external systems during policy evaluation. Accurate but adds latency.
# PIP fetches resource attributes at evaluation time
def get_resource_attributes(resource_id):
return database.query(
"SELECT owner, classification, restricted FROM records WHERE id = ?",
resource_id
)
3. Hybrid Caching: Cache frequently-accessed attributes with appropriate TTLs. Balance freshness vs. performance.
# Tiered attribute fetching
def get_attributes(subject_id, resource_id):
# Level 1: Request context (always fresh)
env_attrs = get_environment_context()
# Level 2: Cached with short TTL (30s)
subject_attrs = cache.get(f"subject:{subject_id}") or fetch_and_cache_subject(subject_id)
# Level 3: Cached with longer TTL (5min) + invalidation
resource_attrs = cache.get(f"resource:{resource_id}") or fetch_and_cache_resource(resource_id)
return {"subject": subject_attrs, "resource": resource_attrs, "environment": env_attrs}
Attribute Schema Governance:
As ABAC deployments grow, attribute sprawl becomes a problem. Without governance:
Best Practices:
subject.department, resource.classification, env.risk_scoreEvery PIP lookup adds latency to authorization. If your latency budget is 10ms and you have 5 attribute sources each taking 5ms, you've already blown your budget. Parallel fetching, aggressive caching, and embedding critical attributes in tokens are essential techniques.
Real ABAC deployments have multiple policies that may apply to a single request. When policies conflict, the system needs deterministic rules for combining decisions. This is handled by policy combining algorithms.
The Conflict Problem:
Consider these policies:
If a doctor requests a restricted record during an emergency, which decision wins?
| Algorithm | Description | Use Case |
|---|---|---|
| Deny-Overrides | Any Deny → final Deny | Security-critical (deny wins) |
| Permit-Overrides | Any Permit → final Permit | Availability-focused (permit wins) |
| First-Applicable | First matching policy wins | Ordered rule lists |
| Only-One-Applicable | Error if multiple apply | Strict, unambiguous policies |
| Deny-Unless-Permit | Deny unless explicit Permit | Default-deny environments |
| Permit-Unless-Deny | Permit unless explicit Deny | Default-allow environments |
Deny-Overrides (Most Common for Security):
Policies: [Permit, NotApplicable, Deny, Permit]
Result: Deny (because at least one Deny exists)
This algorithm ensures that if any policy denies access, the request is denied regardless of permits. It's the safest default for security-sensitive systems.
First-Applicable with Priority:
# OPA example with explicit priority
package authorization
import future.keywords.if
default decision := "deny"
# Priority 1: Hard denies (cannot be overridden)
decision := "deny" if {
input.resource.classification == "top_secret"
input.subject.clearance < 5
}
# Priority 2: Emergency overrides
decision := "permit" if {
input.environment.emergency_mode == true
input.subject.role in ["doctor", "nurse"]
}
# Priority 3: Normal access rules
decision := "permit" if {
input.subject.role == "doctor"
input.subject.id in input.resource.assigned_doctors
}
Hierarchical Policy Organization:
Complex systems organize policies into hierarchies:
Root Policy Set (deny-overrides)
├── Global Denies (system-wide restrictions)
│ ├── Disabled accounts denied
│ └── Maintenance mode denied
├── Resource Policies (first-applicable)
│ ├── Patient Records Policy Set
│ ├── Financial Records Policy Set
│ └── Administrative Policy Set
└── Default Deny (catch-all)
This structure allows:
Policy conflicts cause unpredictable authorization results. Minimize conflicts by: (1) using explicit priority/ordering, (2) making policies disjoint (no overlapping conditions), (3) documenting resolution behavior, and (4) testing combined behavior, not just individual policies.
ABAC's expressiveness comes at a performance cost. Policy evaluation is more complex than RBAC's set membership check. At scale, optimization is critical to meeting latency requirements.
Performance Bottlenecks:
Optimization Strategies:
1. Attribute Preloading and Batching:
# Bad: Sequential attribute fetches
user = fetch_user(user_id) # 5ms
resource = fetch_resource(res_id) # 5ms
env = fetch_environment() # 3ms
# Total: 13ms
# Good: Parallel attribute fetches
import asyncio
user, resource, env = await asyncio.gather(
fetch_user(user_id),
fetch_resource(res_id),
fetch_environment()
)
# Total: max(5ms, 5ms, 3ms) = 5ms
2. Decision Caching with Attribute Hashing:
def check_authorization(subject, action, resource, env):
# Create cache key from relevant attributes
cache_key = hash((
subject['id'], subject['role'],
resource['id'], resource['classification'],
action['name'],
# Don't include highly volatile attributes
))
cached = cache.get(cache_key)
if cached and not is_stale(cached):
return cached.decision
decision = evaluate_policies(subject, action, resource, env)
cache.set(cache_key, decision, ttl=60)
return decision
3. Policy Indexing and Short-Circuiting:
# OPA: Use early exits for common cases
allow if {
# Fast rejection for unauthenticated
input.subject.authenticated == true
# Fast path for admins
input.subject.role == "admin"
}
allow if {
# Only evaluate complex logic if simple checks pass
input.subject.authenticated == true
# ... more complex conditions
}
4. PDP Deployment Topology:
Modern policy engines like OPA and Cedar can evaluate thousands of policies in < 1ms when properly optimized. The bottleneck is almost always attribute fetching, not policy evaluation. Invest optimization effort proportionally.
ABAC and RBAC aren't mutually exclusive—many systems use both. Understanding their trade-offs helps you choose the right model for each part of your system.
| Dimension | RBAC | ABAC |
|---|---|---|
| Mental Model | Users have roles; roles have permissions | Policies evaluate attributes |
| Flexibility | Limited to predefined roles | Any expressible policy |
| Administration | Assign users to roles | Manage policies and attributes |
| Scalability | Role explosion at scale | Policy complexity at scale |
| Instance Control | No (type-level only) | Yes (per-resource policies) |
| Context Awareness | No | Yes (environment attributes) |
| Relationship Handling | Limited | Possible through attributes |
| Performance | Fast (set lookup) | Slower (policy evaluation) |
| Auditability | Easy (who has what roles) | Complex (policy analysis) |
| Implementation | Simple | Complex |
Hybrid RBAC+ABAC Pattern:
The most practical approach often combines both:
package authorization
# Layer 1: Fast RBAC check for coarse access
allow if {
role_check_passes
}
# Layer 2: ABAC refinement for fine-grained control
allow if {
role_check_passes
abac_conditions_met
}
role_check_passes if {
input.subject.roles[_] in allowed_roles[input.action.name]
}
abac_conditions_met if {
input.subject.department == input.resource.department
input.environment.risk_score < 0.5
}
This pattern uses RBAC for initial, fast rejection and ABAC for nuanced decisions on requests that pass role checks.
Start with RBAC. When you find yourself creating roles for every edge case, or when role count exceeds ~100, introduce ABAC for the complex cases while keeping RBAC for the straightforward ones. This evolutionary approach balances simplicity with capability.
We've explored ABAC in depth—its model, architecture, policy languages, and practical implementation considerations. Let's consolidate the key insights:
What's Next:
ABAC policies can become complex and difficult to manage. The next page examines Policy-Based Authorization—strategies for organizing, testing, and governing authorization policies at scale. We'll explore policy design patterns, testing methodologies, and operational practices that keep policy-driven authorization manageable as systems grow.
You now have a comprehensive understanding of Attribute-Based Access Control—its model, architecture, implementation strategies, and trade-offs versus RBAC. You can design ABAC systems, choose appropriate policy languages, and decide when ABAC is the right approach for your authorization needs.