1In a microservice architecture, where should authorization checks typically happen?
Authentication (authn) verifies identity -- who are you? Authorization (authz) verifies permissions -- what can you do? Conflating the two is one of the most common security architecture mistakes in distributed systems.
Authentication and authorization are the two fundamental pillars of access control, yet they are routinely conflated in system design discussions. Authentication (authn) is the process of verifying that an entity is who it claims to be. When a user provides a username and password, or when a service presents a TLS client certificate, the system is performing authentication. The output of authentication is a verified identity: 'this request comes from user alice@example.com' or 'this request comes from the inventory-service.'
Authorization (authz) is the process of determining whether an authenticated identity is permitted to perform a specific action on a specific resource. After authentication establishes that the caller is alice@example.com, authorization checks whether Alice can read document 12345, delete user bob, or invoke the admin API. Authorization is inherently contextual: it depends on the action, the resource, the caller's roles or attributes, and potentially environmental factors like time of day or IP address.
The distinction matters architecturally because authn and authz scale, evolve, and fail differently. Authentication is typically centralized: all services delegate identity verification to a single identity provider (IdP) like Okta, Auth0, or an internal OAuth server. Authorization is typically distributed: each service (or a sidecar policy agent) makes its own permission decisions because it understands its own resource model. A document service knows what 'editor' means for a document; an inventory service knows what 'read-only' means for a warehouse.
In microservice architectures, authentication usually happens once at the API gateway (verify the JWT, extract the identity) and the verified identity is propagated to downstream services via headers or token forwarding. Authorization happens at each service boundary, because each service has different resources and permission models. This separation of concerns is essential: centralizing authorization in the gateway creates a single point of failure and forces all services to share a single permission model.
The most dangerous antipattern is encoding authorization decisions in the authentication token. Putting 'role: admin' in a JWT means the role is frozen for the token's lifetime (often 15-60 minutes). If you revoke the admin role, the user retains admin access until the token expires. Proper architecture keeps identity in the token and checks permissions in real-time against a policy store.
Hotel Key Card Analogy
When you check into a hotel, the front desk verifies your identity (authentication) by checking your ID and reservation. They then issue a key card that opens specific rooms and facilities (authorization). The key card does not prove who you are -- anyone holding it can open the door. Similarly, a JWT proves identity was verified at issuance, but the token itself can be stolen. The hotel can also re-key your card if you extend your stay (dynamic authorization), but if permissions were written permanently onto the card at check-in, changing them would require issuing a new card (the JWT revocation problem).
Google (BeyondCorp)
Google's BeyondCorp model eliminates the concept of a trusted network perimeter. Every request, even from inside Google's network, must be authenticated (who is the user and device?) and authorized (does this user with this device trust level have access to this application?). Authentication uses device certificates and user SSO. Authorization uses a real-time access policy engine that considers user role, device health, location, and time.
AWS (IAM)
AWS IAM separates authentication (proving you are an AWS principal via access keys, roles, or SAML federation) from authorization (IAM policies that grant or deny specific actions on specific resources). Every API call is authenticated, then evaluated against all applicable policies. The policy language supports conditions (ip-based, time-based, MFA-required) for fine-grained authorization.
Spotify
Spotify uses an internal identity platform for authentication across 2,000+ microservices. JWTs carry user identity claims but not permissions. Each service queries a centralized authorization service (backed by Zanzibar-style relationship tuples) to check whether the authenticated user can access the requested resource. This enables instant permission revocation without token invalidation.
| Aspect | Description |
|---|---|
| Centralized vs. Distributed Authorization | Centralized authorization (single policy service) ensures consistency and simplifies auditing but creates a latency and availability bottleneck. Distributed authorization (each service checks locally) is faster and more resilient but risks policy drift and inconsistent enforcement. |
| Token-Based vs. Session-Based Authentication | JWTs are stateless and scalable (no session store) but difficult to revoke before expiry. Sessions are server-side and instantly revocable but require shared state (Redis/DB) across service instances, adding latency and a single point of failure. |
| Coarse-Grained vs. Fine-Grained Authorization | Role-based (RBAC) authorization is simple (admin, editor, viewer) but cannot express resource-level permissions. Attribute-based (ABAC) and relationship-based (ReBAC) authorization supports 'Alice can edit document 123' but adds complexity and policy management overhead. |
| Security vs. User Experience | Strict re-authentication (MFA on every action) maximizes security but frustrates users. Long-lived tokens reduce friction but increase the blast radius of token theft. Step-up authentication (re-verify for sensitive actions) balances both. |
Slack's Shift from RBAC to Relationship-Based Authorization
Scenario
Slack's original authorization model used simple RBAC: workspace owner, admin, member, guest. As Slack added Enterprise Grid (multi-workspace organizations), shared channels, and granular admin roles, the flat RBAC model could not express permissions like 'this admin can manage users in workspace A but not workspace B' or 'this guest can see channel X in workspace Y but not channel Z.'
Solution
Slack migrated to a relationship-based authorization model inspired by Google Zanzibar. Permissions are expressed as relationships: 'user:alice is admin of workspace:acme', 'user:bob is member of channel:general in workspace:acme'. Authorization checks traverse these relationships at query time. The authorization service is centralized but heavily cached, with sub-millisecond latency for common checks.
Outcome
The migration enabled Slack to ship Enterprise Grid features (cross-workspace admin roles, granular channel permissions, compliance controls) without one-off authorization hacks. Permission checks are auditable (every check is logged with the relationship chain), and new permission models can be expressed by adding relationship types without code changes. Latency overhead was <1ms per authorization check due to aggressive caching of relationship tuples.
See Authentication vs Authorization in action
Explore system design templates that use authentication vs authorization and run traffic simulations to see how these concepts perform under real load.
Browse Templates1In a microservice architecture, where should authorization checks typically happen?
2Why is encoding roles directly in JWTs considered an antipattern?