Loading learning content...
Authentication establishes who you are. Authorization determines what you can do. This distinction is fundamental: proving your identity is merely the first step. The second—and equally critical—step is determining which actions that identity may perform and which resources it may access.
Consider your experience with any enterprise system: You log in (authentication), but your experience differs vastly from your colleagues'. A developer sees code repositories and deployment pipelines. An accountant sees financial reports and invoices. A manager sees team performance metrics and approval queues. Same company, same login process, but completely different access—that's authorization at work.
Authorization is the enforcement mechanism of security policy. It translates organizational rules ("Employees see only their department's data") into technical controls (access control lists, role assignments, policy decisions). Get authorization wrong, and sensitive data leaks to unauthorized parties, privileged operations become available to attackers, and the entire security model collapses regardless of how robust authentication might be.
This page explores authorization comprehensively: the theoretical foundations of access control, major authorization models (DAC, MAC, RBAC, ABAC), policy specification and enforcement, common vulnerabilities, and modern authorization patterns including zero trust and policy-as-code approaches.
Authorization answers: "Is this subject allowed to perform this action on this object?"
Every authorization decision involves three key elements:
Authorization decisions flow through multiple stages:
[Subject] --- requests ---> [Action on Object]
|
v
[Policy Decision Point]
|
+---------+---------+
| | |
v v v
[Allow] [Deny] [Conditional]
Policy Decision Point (PDP): The component that evaluates access requests against policy rules and returns a decision.
Policy Enforcement Point (PEP): The component that intercepts access requests, queries the PDP, and enforces the decision.
Policy Information Point (PIP): Sources of attribute information (user groups, resource classifications, environmental context) used in authorization decisions.
These concepts are frequently confused but fundamentally different. You can be authenticated (identity proven) but unauthorized (not permitted access). You might authenticate as 'Bob' but lack authorization to access payroll data. Conversely, anonymous access policies might authorize actions without any authentication. Keep these concepts distinct in system design and troubleshooting.
Access control models provide theoretical frameworks for organizing and enforcing authorization. Each model embodies different assumptions about who defines policy and how enforcement operates.
In DAC, resource owners control access permissions at their discretion. The creator of a file decides who else may access it.
Characteristics:
Implementation Examples:
Advantages:
Disadvantages:
In MAC, a central authority defines access policy based on security classifications. Users cannot override these rules, regardless of object ownership.
Characteristics:
Security Models:
Bell-LaPadula (Confidentiality):
Biba (Integrity):
Implementation Examples:
Advantages:
Disadvantages:
| Aspect | Discretionary (DAC) | Mandatory (MAC) |
|---|---|---|
| Policy Authority | Resource owners | Central administration |
| User Autonomy | Users control their resources | Users cannot override policy |
| Flexibility | High (users adapt as needed) | Low (rigid policy structure) |
| Enforcement | Voluntary compliance | System-enforced, mandatory |
| Typical Use | Commercial systems, personal data | Military, government, high-security |
| Trojan Resistance | Vulnerable (inherits user rights) | Resistant (compartmentalized) |
Role-Based Access Control (RBAC) bridges the gap between DAC flexibility and MAC structure. Permissions are assigned to roles, and users are assigned to roles. Users acquire permissions indirectly through their role memberships.
Role: A collection of permissions that corresponds to a job function (e.g., "Developer," "Manager," "Auditor").
Permission: The ability to perform an action on a resource (e.g., "read:contracts," "deploy:production").
Role Assignment: Associating users with roles.
Role Hierarchy: Roles can inherit permissions from other roles (Senior Developer inherits from Developer).
The foundational model with three components:
User --> Role --> Permission --> Resource
Example:
Alice --> "Developer" --> ["read:code", "write:code", "run:tests"]
Bob --> "Manager" --> ["read:code", "read:reports", "approve:releases"]
Adds role inheritance: senior roles inherit permissions from junior roles.
Senior Developer
|
Developer
/ \
Frontend Dev Backend Dev
Senior Developer automatically has all permissions of Developer, which has permissions from both specialized roles.
Adds constraints that limit role assignments:
Static Separation of Duties (SSD): Mutually exclusive roles that cannot be assigned to the same user:
Dynamic Separation of Duties (DSD): Roles that cannot be activated simultaneously:
Cardinality Constraints:
Combines hierarchies (RBAC1) and constraints (RBAC2) for complete flexibility.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
/** * RBAC Implementation Pattern * * This demonstrates a clean RBAC implementation suitable * for application-level authorization. */ interface Permission { action: 'create' | 'read' | 'update' | 'delete' | 'execute'; resource: string; conditions?: Record<string, unknown>; // Optional attribute conditions} interface Role { name: string; permissions: Permission[]; inheritsFrom?: string[]; // Role hierarchy} interface User { id: string; roles: string[]; attributes: Record<string, unknown>; // For ABAC-style conditions} class RBACAuthorizer { private roles: Map<string, Role> = new Map(); /** * Register a role with its permissions */ registerRole(role: Role): void { this.roles.set(role.name, role); } /** * Get all permissions for a role, including inherited */ private getEffectivePermissions(roleName: string, visited = new Set<string>()): Permission[] { if (visited.has(roleName)) { return []; // Prevent circular inheritance } visited.add(roleName); const role = this.roles.get(roleName); if (!role) return []; // Start with direct permissions const permissions = [...role.permissions]; // Add inherited permissions if (role.inheritsFrom) { for (const parentRole of role.inheritsFrom) { permissions.push(...this.getEffectivePermissions(parentRole, visited)); } } return permissions; } /** * Check if a user is authorized for an action on a resource */ isAuthorized( user: User, action: Permission['action'], resource: string, context?: Record<string, unknown> ): boolean { // Collect all permissions from all roles const allPermissions: Permission[] = []; for (const roleName of user.roles) { allPermissions.push(...this.getEffectivePermissions(roleName)); } // Check if any permission matches return allPermissions.some(permission => { // Match action if (permission.action !== action) return false; // Match resource (supports wildcards) if (!this.matchResource(permission.resource, resource)) return false; // Evaluate conditions if present if (permission.conditions) { return this.evaluateConditions(permission.conditions, user, context); } return true; }); } private matchResource(pattern: string, resource: string): boolean { // Support wildcard matching if (pattern === '*') return true; if (pattern.endsWith('*')) { return resource.startsWith(pattern.slice(0, -1)); } return pattern === resource; } private evaluateConditions( conditions: Record<string, unknown>, user: User, context?: Record<string, unknown> ): boolean { // Simple condition evaluation // In production, use a policy engine like Open Policy Agent for (const [key, expected] of Object.entries(conditions)) { const actual = user.attributes[key] ?? context?.[key]; if (actual !== expected) return false; } return true; }} // Example usageconst authorizer = new RBACAuthorizer(); // Define roles with hierarchyauthorizer.registerRole({ name: 'viewer', permissions: [ { action: 'read', resource: 'documents/*' } ]}); authorizer.registerRole({ name: 'editor', inheritsFrom: ['viewer'], permissions: [ { action: 'create', resource: 'documents/*' }, { action: 'update', resource: 'documents/*' } ]}); authorizer.registerRole({ name: 'admin', inheritsFrom: ['editor'], permissions: [ { action: 'delete', resource: 'documents/*' }, { action: 'read', resource: 'admin/*' }, { action: 'update', resource: 'admin/*' } ]}); // Check authorizationconst user: User = { id: 'alice', roles: ['editor'], attributes: { department: 'engineering' }}; console.log(authorizer.isAuthorized(user, 'read', 'documents/report.pdf')); // true (inherited)console.log(authorizer.isAuthorized(user, 'update', 'documents/report.pdf')); // true (direct)console.log(authorizer.isAuthorized(user, 'delete', 'documents/report.pdf')); // false (admin only)Keep roles aligned with job functions, not individual permissions. Create a role hierarchy that matches organizational structure. Avoid 'permission creep' by regularly reviewing role assignments. Use constraints (SSD/DSD) to enforce separation of duties. Document the purpose of each role to guide assignment decisions.
Attribute-Based Access Control (ABAC) provides the most flexible authorization model. Access decisions consider multiple attributes of subjects, objects, actions, and the environment—not just role membership.
Subject Attributes: Properties of the requester
Object Attributes: Properties of the resource
Action Attributes: Properties of the requested operation
Environment (Context) Attributes: Situational properties
Policy: "Doctors can view patient records during working hours from hospital devices, but only for patients they are treating."
policy ViewPatientRecords {
subject.role == "doctor" AND
object.type == "patient_record" AND
subject.treatmentRelationship CONTAINS object.patientId AND
environment.timeOfDay BETWEEN "07:00" AND "19:00" AND
environment.deviceLocation == "hospital_network"
}
This policy references:
role, treatmentRelationshiptype, patientIdtimeOfDay, deviceLocationeXtensible Access Control Markup Language (XACML) is the OASIS standard for ABAC policy specification and evaluation.
XACML Architecture:
XACML Decision Values:
Permit — Access allowedDeny — Access deniedIndeterminate — Error or missing informationNotApplicable — No policy covers this requestOPA has emerged as the de facto standard for policy-as-code in cloud-native environments:
# OPA Policy in Rego language
package authorization
default allow = false
allow {
input.method == "GET"
input.path == ["documents", _]
input.user.department == "engineering"
}
allow {
input.method == "PUT"
input.path == ["documents", doc_id]
input.user.id == data.documents[doc_id].owner
}
Use RBAC when access aligns with job functions and doesn't require dynamic conditions. Use ABAC when context matters (time, location, resource properties) or when policies are too nuanced for discrete roles. Many systems combine both: RBAC for basic structure plus ABAC for fine-grained refinement within roles.
Authorization failures are among the most common and severe security vulnerabilities. OWASP consistently ranks Broken Access Control as a top web application risk.
Occurs when an application exposes internal object identifiers without proper authorization checks:
Vulnerable Pattern:
GET /api/documents/12345 → Returns document 12345
GET /api/documents/12346 → Returns document 12346 (unauthorized!)
The application checks if the user is authenticated but doesn't verify they're authorized to access that specific document.
Mitigation:
Vertical Escalation: Gaining higher privileges than assigned
Horizontal Escalation: Accessing resources of peers
Causes:
| Vulnerability | Description | Impact | Prevention |
|---|---|---|---|
| IDOR | Manipulating object references to access unauthorized data | Data breach, unauthorized access | Object-level authorization checks |
| Missing Function-Level Control | No authorization on administrative endpoints | Privilege escalation | Authorization on every endpoint |
| Parameter Tampering | Modifying role/permission parameters in requests | Privilege escalation | Server-side authorization, ignore client role claims |
| Path Traversal | Bypassing authorization via URL manipulation | Unauthorized file access | Canonicalize paths, enforce access control |
| JWT Manipulation | Modifying token claims to elevate privileges | Impersonation, escalation | Signature verification, server-side claims |
| Forced Browsing | Accessing unlinked but unprotected URLs | Information disclosure | Authorization on all resources |
Missing Authorization Check:
# VULNERABLE: No authorization check!
@app.route('/admin/users/<user_id>', methods=['DELETE'])
def delete_user(user_id):
User.delete(user_id)
return {'status': 'deleted'}
# SECURE: Verify admin role
@app.route('/admin/users/<user_id>', methods=['DELETE'])
@require_role('admin') # Decorator enforces authorization
def delete_user(user_id):
User.delete(user_id)
return {'status': 'deleted'}
Client-Side Authorization (Dangerous):
// VULNERABLE: Authorization enforced only in UI
if (user.role === 'admin') {
showAdminPanel(); // Attackers bypass this trivially
}
// SECURE: Server-side enforcement
// API returns 403 Forbidden for non-admins
// UI state reflects what server authorizes
Mass Assignment:
# VULNERABLE: Accepting all user-provided fields
@app.route('/profile', methods=['PUT'])
def update_profile():
current_user.update(**request.json) # Attacker sets {"role": "admin"}
# SECURE: Whitelist updatable fields
@app.route('/profile', methods=['PUT'])
def update_profile():
allowed = ['name', 'email', 'avatar']
updates = {k: v for k, v in request.json.items() if k in allowed}
current_user.update(**updates)
First American Financial exposed 885 million sensitive documents—mortgage applications, social security numbers, bank statements—due to an IDOR vulnerability. Documents were accessible by simply changing the document ID in the URL. No authentication was required. Authorization checks existed for authenticated users but were completely absent for unauthenticated access. This single missing check exposed a decade of customer data.
Building robust authorization requires systematic design, consistent implementation, and continuous validation.
1. Centralize Authorization Logic
Don't scatter authorization checks throughout the codebase. Centralize in a policy service that all components consult:
┌─────────────────────┐
│ Policy Decision │
│ Point (PDP) │
└──────────┬──────────┘
│
┌──────────┬───────────┼───────────┬──────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[API Gateway] [Service A] [Service B] [Service C] [UI]
2. Deny by Default
Implement fail-secure behavior: if authorization cannot be determined, deny access.
def check_authorization(user, resource, action) -> bool:
try:
return policy_engine.evaluate(user, resource, action)
except Exception:
log.error("Authorization decision failed - denying access")
return False # Fail secure
3. Layer Authorization Checks
Enforce authorization at multiple levels:
The most secure approach: filter unauthorized data at the query level, not in application code.
Insecure Pattern (Filter After Fetch):
-- Fetch ALL documents, then filter in code
SELECT * FROM documents;
-- Application code
filtered = [d for d in documents if d.owner_id == current_user.id]
Secure Pattern (Filter at Query):
-- Only fetch documents user is authorized to see
SELECT * FROM documents
WHERE owner_id = :current_user_id
OR id IN (SELECT document_id FROM shared_access WHERE user_id = :current_user_id);
Row-Level Security (PostgreSQL):
-- Enable RLS on table
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Policy: Users see only their documents and shared documents
CREATE POLICY user_document_access ON documents
USING (
owner_id = current_setting('app.current_user_id')::int
OR id IN (
SELECT document_id FROM shared_access
WHERE user_id = current_setting('app.current_user_id')::int
)
);
Modern authorization increasingly treats policies as code—version controlled, tested, and deployed like application code. Tools like Open Policy Agent, Cedar, and Casbin enable this approach. Benefits include reproducibility, testability, auditability, and the ability to review policy changes just like code changes.
Distributed systems complicate authorization: how do services authorize requests when they can't see the original user context?
JWT-Based Authorization:
Encoded claims flow with requests through the service mesh:
[User] ---(JWT)---> [API Gateway] ---(JWT)---> [Service A] ---(JWT)---> [Service B]
JWT Payload:
{
"sub": "user123",
"roles": ["editor"],
"permissions": ["read:documents", "write:documents"],
"tenant": "acme-corp",
"exp": 1704067200
}
Each service validates the token signature and makes authorization decisions based on claims.
Limitations:
Zero Trust extends authorization beyond yes/no decisions to continuous, risk-based evaluation:
Principles:
Contextual Authorization Signals:
Dynamic Access Decisions:
if (risk_score > threshold) {
require_step_up_authentication();
}
if (device.compliance == 'non_compliant') {
limit_access_to_low_sensitivity();
}
if (location == 'unknown' && time == 'unusual') {
trigger_additional_verification();
}
Authorization based on relationships between entities rather than static attributes:
// Google Zanzibar-style relationship tuples
document:report#viewer@user:alice
document:report#owner@user:bob
folder:engineering#member@group:engineers
group:engineers#member@user:alice
// Query: Can alice view report?
// Traverses: alice -> engineers -> engineering folder -> inherited viewer
ReBAC excels when access depends on how entities relate: file sharing, team membership, hierarchical resources.
Authorization—determining what authenticated entities may do—is essential for enforcing security policy. Without proper authorization, even perfect authentication leaves systems vulnerable. Let's consolidate the key concepts:
What's Next:
With authentication (proving identity) and authorization (controlling access) established, we now explore Non-repudiation—the mechanisms that ensure entities cannot deny having performed actions, providing the accountability and auditability essential for security governance.
You now understand authorization in comprehensive depth—from theoretical access control models through practical implementation patterns and modern architectures. You can design authorization systems, identify vulnerabilities, and select appropriate models for different security requirements.