Authorization
Authorization controls what authenticated users can do. RBAC, ABAC, and policy engines enforce permissions across distributed systems without hardcoding.
Authorization answers: what are you allowed to do? After authentication confirms identity, authorization enforces permissions. In a monolith, a simple role check suffices. In microservices, you need a consistent policy engine that every service queries — otherwise each team implements its own rules and access control becomes unpredictable. The three dominant models are RBAC (role-based), ABAC (attribute-based), and ReBAC (relationship-based, used by Google Zanzibar).
| Aspect | Details |
|---|---|
| What it is | The process of determining what actions an authenticated user is permitted to perform on which resources |
| When to use | Any multi-user system, multi-tenant SaaS, API access control, data isolation between customers |
| When NOT to use | Single-user applications or internal tools where all users need the same level of access |
| Real-world example | Google Zanzibar handles authorization for Google Drive, YouTube, and Cloud IAM — trillions of checks per second |
| Interview tip | Always separate authentication from authorization — they are different concerns with different solutions |
| Common mistake | Hardcoding permission checks (if user.role == 'admin') throughout the codebase instead of using a policy engine |
| Key tradeoff | Granularity vs performance — fine-grained permissions are more secure but require more checks per request |
Why This Matters
Authorization failures are the second most common web vulnerability (OWASP A01:2021 Broken Access Control). In multi-tenant SaaS, one tenant seeing another's data is a company-ending bug. In microservices, authorization logic scattered across 50 services is impossible to audit. Centralizing authorization in a policy engine (OPA, Cedar, SpiceDB) lets you enforce consistent rules, audit who accessed what, and change permissions without redeploying code.
The Building Blocks
- RBAC (Role-Based Access Control): Users are assigned roles (admin, editor, viewer), roles have permissions. Simple, widely understood, works for 80% of applications. Breaks down when permissions depend on resource ownership.
- ABAC (Attribute-Based Access Control): Permissions depend on attributes of the user, resource, action, and context. Example: 'managers can approve expenses under $10,000 during business hours.' More flexible than RBAC but harder to reason about.
- ReBAC (Relationship-Based Access Control): Permissions defined by relationships between objects. Google Zanzibar: 'user:alice is editor of doc:readme, editors can view.' Natural for document/file sharing systems.
- Policy Engine: A dedicated service that evaluates authorization decisions. OPA (Open Policy Agent) uses Rego language, AWS Cedar uses a typed policy language. Decouples policy from code.
- Permission Propagation: In microservices, the API gateway validates authentication and attaches user claims/roles. Downstream services make local authorization decisions based on those claims without calling back to the auth service.
Under the Hood
When a request arrives, the API gateway extracts the user's identity from the JWT and enriches it with roles/groups (cached from the identity provider). It forwards the request with these claims to the target service. The service calls the policy engine: "Can user X perform action Y on resource Z?" The policy engine evaluates the request against stored policies and returns allow/deny.
For performance, authorization decisions are cached. Google Zanzibar uses a distributed cache with a consistency model — it guarantees that permission changes propagate within a bounded time (called "zookies" for consistency tokens). In simpler systems, caching RBAC role lookups in Redis with a 5-minute TTL balances performance and freshness.
The critical design decision is where to enforce authorization. Gateway-level enforcement (coarse: can this user access this API?) catches most unauthorized requests cheaply. Service-level enforcement (fine: can this user edit THIS document?) handles resource-specific permissions. Most systems need both layers.
How Companies Actually Do This
Google built Zanzibar to handle authorization for Drive, YouTube, Cloud IAM, and Maps. It serves trillions of authorization checks per second with single-digit millisecond latency using a global distributed graph.
AWS implements authorization through IAM policies — JSON documents that define who can do what on which resources. Every AWS API call is authorized through IAM, processing billions of decisions daily.
Airbnb uses a centralized authorization service called Himeji, built on SpiceDB (open-source Zanzibar). It handles complex relationships: host owns listing, guest booked listing, co-host manages listing.
Common Pitfalls
- Scattering authorization checks across services with inconsistent logic — one service allows access that another denies for the same user
- Checking only roles without verifying resource ownership — a user with 'editor' role should only edit THEIR documents, not all documents
- Not logging authorization denials — failed access attempts are critical security signals for detecting brute-force or privilege escalation attacks
Interview Questions Worth Practicing
- How would you design an authorization system for a multi-tenant SaaS platform?
- What are the tradeoffs between RBAC and ABAC, and when would you choose each?
- How does Google Zanzibar achieve consistent authorization at global scale?
The Tradeoffs
- RBAC Simplicity vs ABAC Flexibility: RBAC is easy to understand and audit but cannot express context-dependent rules. ABAC handles complex scenarios but policies become hard to reason about at scale.
- Centralized vs Distributed Enforcement: A central policy engine ensures consistency but adds latency. Distributed enforcement (cached policies) is faster but risks stale permissions.
- Granularity vs Performance: Object-level permissions (can user edit THIS row?) are the most secure but require a check per object. Role-level permissions are faster but coarser.
How to Explain This in an Interview
Here is how I would explain Authorization in a system design interview:
Authorization decides what a user can do after authentication confirms who they are. I would implement it in two layers: the API gateway enforces coarse checks (does this user's role allow access to the /admin endpoints?), and individual services enforce fine-grained checks (can this user edit THIS specific document?). For the policy engine, I would use RBAC for most resources since it covers 80% of cases simply. For complex sharing scenarios (like Google Docs), I would use a relationship-based model similar to Zanzibar — define permissions as relationships (user:alice is editor of doc:X) and traverse the graph. I would cache authorization decisions in Redis with short TTLs and log every denial for security auditing.
Related Topics
The Real-World Incident That Made This Famous
Understanding Authorization became critical after multiple high-profile production incidents at major tech companies. When systems handle millions of users, even small misunderstandings about Authorization can lead to cascading failures that cost millions in lost revenue and erode user trust. Companies like Netflix, Google, Amazon, and Meta have all invested heavily in mastering Authorization because they learned the hard way that ignoring it leads to outages.
The key lesson from these incidents: Authorization is not just a theoretical concept — it is a practical skill that separates engineers who build resilient systems from those who build fragile ones. Every major outage report from the past decade involves at least one Authorization-related design decision that was either implemented incorrectly or overlooked entirely during the initial architecture review.
How Senior Engineers Think About This
Senior engineers approach Authorization differently from textbook definitions. Instead of memorizing rules, they build mental models. They ask: "What problem does Authorization solve? When does it fail? What are the alternatives?" This problem-first thinking leads to better design decisions because every system has unique constraints.
When evaluating Authorization in a system design context, experienced engineers consider the failure modes first. What happens when this component goes down? How does the system degrade? Is the degradation graceful or catastrophic? These questions reveal more about your understanding than any textbook definition.
The key difference between junior and senior engineers when it comes to Authorization: juniors focus on the happy path, while seniors design for what happens when things go wrong. They consider operational cost, team expertise, monitoring requirements, and how the decision will look six months from now when traffic has grown 10x.
Common Interview Mistakes
Mistake 1: Giving a textbook definition without context. Interviewers want to see you connect Authorization to real systems and real problems. Instead of reciting definitions, explain when and why you would use Authorization in the system you are designing.
Mistake 2: Not discussing trade-offs. Every design decision involving Authorization has trade-offs. Discuss what you gain and what you give up. Acknowledge the downsides and explain why the benefits outweigh them for your specific use case.
Mistake 3: Overcomplicating the solution. Start with the simplest approach to Authorization that meets the requirements, then add complexity only when justified. Many candidates jump to complex implementations when a simpler solution would work perfectly.
Production Checklist
- Define clear metrics for measuring the effectiveness of your Authorization implementation
- Set up monitoring and alerting that specifically tracks Authorization-related failures
- Document your Authorization design decisions in Architecture Decision Records (ADRs)
- Test failure scenarios related to Authorization in staging before production deployment
- Review and update your Authorization implementation quarterly as system requirements evolve
- Train new team members on the specific Authorization patterns used in your system
- Establish runbooks for common Authorization-related incidents and recovery procedures
Practical Implementation for .NET Developers
In ASP.NET Core, use policy-based authorization: services.AddAuthorization(o => o.AddPolicy("CanEdit", p => p.RequireClaim("role","editor"))). Apply with [Authorize(Policy = "CanEdit")]. For resource-based authorization, implement IAuthorizationHandler<OperationAuthorizationRequirement, Document> to check ownership. For complex RBAC, use a permissions table in the database and a custom IAuthorizationHandler that queries it. Consider integrating with OpenFGA (open-source Zanzibar) via its .NET SDK for relationship-based access control.
ASP.NET Core setup: Create a service class that encapsulates the logic, register it with dependency injection, and inject it into your controllers or minimal API endpoints. The built-in DI container handles lifecycle management.
Entity Framework Core: For database interactions, EF Core provides the ORM layer. Use migrations for schema management and raw SQL for performance-critical queries. Consider Dapper for read-heavy paths where EF Core overhead matters.
Azure integration: If deploying to Azure, leverage managed services — Azure Cache for Redis, Azure SQL, Azure Service Bus, Azure Cosmos DB. These eliminate operational overhead and provide built-in monitoring through Application Insights.
Testing: Use xUnit with Testcontainers for integration tests that spin up real databases in Docker. Mock external dependencies with NSubstitute. The WebApplicationFactory class lets you test your entire HTTP pipeline in-process.
Monitoring: Add Application Insights telemetry to track request latency, dependency calls, and custom metrics. Use structured logging with Serilog to make production debugging possible:
Log.Information("Processing {Operation} for {ResourceId}", operation, resourceId);
This gives you searchable, structured logs in Azure Monitor or Seq.