Loading learning content...
Imagine a massive corporate headquarters with thousands of employees. At the entrance, security guards verify each person's identity—checking badges, comparing faces to photos, perhaps even requiring a fingerprint scan. This is authentication: proving you are who you claim to be.
But once inside the building, a new challenge arises. Can this employee access the executive boardroom? Can they view payroll data? Can they modify the production database? Can they deploy code to live systems? This is authorization: determining what an authenticated entity is allowed to do.
In modern distributed systems, authorization is one of the most critical—and most commonly misunderstood—security concerns. A single authorization bypass can expose sensitive data to unauthorized users, allow privilege escalation attacks, or enable complete system compromise. Yet authorization is also extraordinarily complex, involving nuanced business rules, dynamic contexts, and intricate permission hierarchies that must be enforced consistently across dozens or hundreds of microservices.
By the end of this page, you will understand authorization as a distinct security domain, know how it differs from authentication, grasp the core concepts and terminology, and appreciate why authorization is particularly challenging in distributed architectures. This foundation is essential before exploring specific authorization patterns.
Before diving into authorization patterns, we must establish an absolutely clear distinction between authentication and authorization. These terms are frequently confused, abbreviated identically (AuthN vs AuthZ), and sometimes incorrectly used interchangeably—even by experienced engineers.
Authentication (AuthN) answers the question: "Who are you?"
Authentication establishes identity. It verifies that a user, service, or system is genuinely who or what it claims to be. Authentication mechanisms include passwords, biometrics, cryptographic certificates, API keys, and multi-factor authentication. The output of authentication is an identity assertion—a claim that "this request comes from User X" or "this service is Service Y."
Authorization (AuthZ) answers the question: "What are you allowed to do?"
Authorization determines permissions. Given a verified identity, authorization decides whether that identity can perform a specific action on a specific resource. Authorization mechanisms include role-based access control, attribute-based policies, access control lists, and capability tokens. The output of authorization is a permit/deny decision for a specific operation.
| Aspect | Authentication (AuthN) | Authorization (AuthZ) |
|---|---|---|
| Core Question | Who are you? | What can you do? |
| Input | Credentials (passwords, tokens, certificates) | Identity + Resource + Action |
| Output | Verified identity or rejection | Permit or deny decision |
| Timing | Usually at session start or request initiation | Every protected operation |
| Failure Mode | Login failure, invalid credentials | Access denied, forbidden |
| HTTP Status Code | 401 Unauthorized | 403 Forbidden |
| Example Decision | "Is this really Alice?" | "Can Alice delete this file?" |
| State | Often stateless (JWT) or session-based | Can be stateless or require context lookup |
| Frequency | Once per session or per request | Multiple times per request possible |
Many security breaches occur when developers conflate authentication with authorization. A system might correctly verify that a request comes from an authenticated user, but then fail to check whether that user has permission to perform the requested action. This results in broken access control—consistently ranked among the top web application security risks by OWASP.
The dependency relationship:
Authorization typically depends on authentication (you must know who someone is before deciding what they can do), but authentication never depends on authorization. This creates a natural flow:
Request → Authentication → Identity Established → Authorization → Permission Decision → Action Executed
However, this linear view oversimplifies reality. In complex systems, a single request might require multiple authorization decisions at different layers, and some authorization contexts might not require user authentication at all (e.g., anonymous users with limited permissions, or machine-to-machine communication with service identities).
Authorization systems revolve around a set of fundamental concepts that appear regardless of the specific pattern or implementation. Understanding this vocabulary is essential for designing and discussing authorization architectures.
The Authorization Question:
Every authorization decision can be framed as answering this question:
"Given subject S, can S perform action A on resource R, within context C?"
This is sometimes called the authorization tuple: (S, A, R, C) → {permit, deny}
Different authorization patterns provide different mechanisms for answering this question:
Each approach has distinct advantages for different use cases, as we'll explore in subsequent pages.
When designing authorization logic, always think in terms of the complete tuple: WHO is trying to do WHAT to WHICH resource under WHAT circumstances. Incomplete reasoning—like "admins can do this"—often leads to security gaps because it doesn't specify the action or resource scope.
Effective authorization requires careful data modeling. The relationships between subjects, resources, permissions, and policies form the foundation of any access control system. Let's examine the key modeling decisions and patterns.
Subject Identity Model:
Subjects in an authorization system typically include:
Subjects can have multiple concurrent identities (a user might be part of several groups, have multiple roles, and be accessing from different devices). The authorization system must resolve these overlapping identities into coherent permission sets.
Resource Hierarchy Model:
Resources are rarely flat—they exist in hierarchies that affect permission inheritance:
Organization
├── Team A
│ ├── Project X
│ │ ├── Document 1
│ │ └── Document 2
│ └── Project Y
│ └── Document 3
└── Team B
└── Project Z
└── Document 4
Key modeling questions include:
Permission Granularity:
Permissions can be modeled at varying levels of granularity:
Coarse-grained: admin, editor, viewer
Fine-grained: documents:read, documents:write, documents:delete, documents:share
Instance-level: documents:12345:read, documents:12345:write
Most real-world systems use a hybrid approach: coarse-grained roles for general access patterns, with fine-grained permissions for sensitive operations and instance-level controls for high-value resources.
As systems grow, the number of possible (subject, action, resource) combinations explodes combinatorially. With 10,000 users, 50 actions, and 1,000,000 resources, you have 500 billion possible permission combinations. No system can store or evaluate this directly—authorization patterns are essentially compression and caching strategies for this vast permission space.
Authorization becomes significantly more complex in distributed architectures. What seems straightforward in a monolithic application—checking if a user can perform an action—becomes a distributed systems challenge when microservices are involved.
The Distributed Authorization Challenges:
Architectural Patterns for Distributed Authorization:
| Pattern | Description | Pros | Cons |
|---|---|---|---|
| Gateway Enforcement | Single API gateway handles all authorization | Centralized, simple | Limited context, gateway becomes bottleneck |
| Embedded Enforcement | Each service enforces its own authorization | High performance, full context | Policy sprawl, consistency challenges |
| Sidecar Enforcement | Authorization sidecar proxy for each service | Consistent policy, service isolation | Operational complexity, latency overhead |
| Centralized PDP | All services call central Policy Decision Point | Single source of truth, easy updates | Single point of failure, latency |
| Distributed PDP | Multiple PDP instances with synchronized policies | High availability, good performance | Synchronization complexity, eventual consistency |
The Zero Trust Imperative:
Modern authorization must embrace zero trust principles: never assume a request is authorized just because it came from an internal service or passed through a gateway. Every service should validate authorization independently, even for internal traffic. This defense-in-depth approach prevents lateral movement if any single service is compromised.
This means authorization checks are not optional—they're mandatory at every trust boundary in your architecture.
An authorization decision follows a specific lifecycle from request initiation through decision enforcement. Understanding this lifecycle helps identify where bottlenecks occur and where to optimize.
Phase 1: Request Interception
Authorization begins when a protected resource is accessed. Interception can occur at:
The interception point determines what context is available and what latency is acceptable.
Phase 2: Context Assembly
Before making a decision, the authorization system gathers necessary context:
Context assembly can require database lookups, cache reads, or calls to external services. Efficient context assembly is often the key to authorization performance.
Phase 3: Policy Evaluation
With context assembled, policies are evaluated to reach a decision:
Policy engines vary from simple if-then rules to sophisticated domain-specific languages like Rego (Open Policy Agent) or Cedar (AWS).
Phase 4: Decision Caching
To reduce latency, authorization decisions are often cached:
Caching authorization decisions is inherently risky—stale cache entries can grant or deny access incorrectly.
Phase 5: Decision Enforcement
The final phase enforces the decision:
The fastest authorization decision is one you never have to make. Use coarse-grained checks early ("is user authenticated?") to reject obviously invalid requests before expensive fine-grained authorization. Layer your defenses from cheap to expensive.
Authorization vulnerabilities are among the most dangerous and most common security flaws. OWASP consistently ranks broken access control as a top-10 vulnerability. Understanding failure modes helps design more robust systems.
/api/invoices/123 to /api/invoices/456 to view another user's invoice.isAdmin=true to a request body that's blindly trusted./admin/ endpoints that lack authorization checks because "nobody knows the URL."Real-World Authorization Breach Example:
In 2021, a major social media platform experienced a data exposure incident where users could access private photos of other users by incrementing numeric photo IDs in API calls. The system authenticated users correctly but failed to verify that the requesting user had authorization to view each specific photo. This is a classic IDOR vulnerability.
The fix required implementing proper resource-level authorization: before returning any photo, verify that the requesting user has a relationship (friend, follower, owner) that grants access. This seemingly simple check requires efficient relationship queries at scale—millions of authorization checks per second.
Defensive Principles:
Authorization enforcement can occur at multiple layers of a system architecture. Each layer offers different trade-offs between security, performance, and development complexity. Defense-in-depth recommends enforcement at multiple points.
| Layer | What It Protects | Advantages | Limitations |
|---|---|---|---|
| Network/Firewall | Network access, service-to-service communication | Very fast, blocks at network level | Coarse-grained, no user context |
| API Gateway | External API access, route-level permissions | Centralized, consistent for all services | Limited request context, can't protect internal traffic |
| Service Mesh | Service-to-service, mTLS, basic policies | Infrastructure-level, transparent to apps | Policy limitations, latency overhead |
| Application Middleware | HTTP endpoints, request-level authorization | Rich context, framework integration | Duplicated across services, developer dependent |
| Business Logic | Domain operations, fine-grained actions | Full context, complex rules possible | Scattered logic, hard to audit |
| Data Access Layer | Row/column level security, query filtering | Prevents data leakage at source | Database-specific, limited policy expressiveness |
Layered Enforcement Example:
Consider a healthcare application where doctors access patient records:
An attacker would need to bypass all five layers to access unauthorized records. This defense-in-depth approach significantly raises the bar for successful attacks.
A common anti-pattern is relying solely on API gateway authorization, assuming internal services are "trusted." This fails when: (1) an internal service is compromised, (2) traffic bypasses the gateway, or (3) services are exposed directly. Always enforce authorization at the service level, not just the edge.
We've established a deep foundation for understanding authorization as a critical security concern in system design. Let's consolidate the key insights:
What's Next:
With this foundation in place, we'll explore specific authorization patterns in detail. The next page examines Role-Based Access Control (RBAC)—the most widely deployed authorization pattern, used everywhere from operating system permissions to enterprise SaaS applications. We'll explore how RBAC models permissions through roles, its strengths, its limitations, and best practices for implementation at scale.
You now understand authorization as a distinct security domain, know the core terminology and concepts, and appreciate the challenges of distributed authorization. This foundation prepares you to master specific authorization patterns and implement secure access control in your systems.