ChibihamChibiham
Evolution of AuthN/AuthZ Architecture (2): API Gateway and Microservices
🌐

Evolution of AuthN/AuthZ Architecture (2): API Gateway and Microservices

This series consists of five parts.

In Part 1 we introduced an identity platform (IdP), centralizing login and user management. But as the number of APIs grows and the monolith starts to split apart, a new problem emerges: governing the token-receiving side (the Resource Server). Part 2 deals with this.

Before Stage 2: The Breakdown of "Each Service Validates Tokens Independently"

The natural setup right after introducing an IdP looks like this. Each API service embeds a JWT library, fetches the public key from the IdP's JWKS endpoint, and verifies the signature on its own.

With three services this works fine. But once you have 10 or 30, the following problems surface.

  1. Inconsistent implementations: One service only checks exp and doesn't validate aud; another uses an old version of a library that permits alg: none. There are as many validation implementations as services, and the whole system is bottlenecked by the weakest one.
  2. Cross-cutting policies can't be applied: "Add rate limiting to all APIs" or "emergency-block a specific token issuer" becomes a request handed to every team. The speed of incident response is bound to the speed of organizational communication.
  3. Duplicated investment per language/framework: You must build and maintain validation middleware separately for Go, Java, Python, and Node.
  4. Auditing difficulty: Nobody holds the full picture of "which API makes what authorization decision."

In other words, the essence of the problem is not technical but governance. Recognizing this, I believe, is the key to understanding the API Gateway not as a "convenient reverse proxy" but as an "enforcement point for governance."

Stage 2: Centralizing Authentication with an API Gateway

The Gateway stands at the entrance to all requests and offloads cross-cutting concerns such as token validation, rate limiting, audit logging, and TLS termination. Each service can then focus on business logic.

Why It's Always Discussed Together with the Rise of Microservices

The need for an API Gateway is inseparable from the rise of microservices. Breaking down the causality:

  • The value of microservices is that "each team can deploy independently," so the number of services grows in proportion to the number of teams in the organization.
  • The more services there are, the worse the "inconsistency, duplication, unauditability" problems above become—exponentially.
  • At the same time, from the client's perspective, another problem arises: "the endpoints are split across services and are hard to call," creating demand for a single entry point.
  • The point where these two demands (governance and aggregation) converge is the API Gateway.

Conversely, if you have only a handful of services and a single team, a Gateway is often overkill. nginx plus a shared middleware library is frequently enough, and you should soberly assess whether the adoption and operational cost of a Gateway product (which is itself a distributed system) can be justified.

Patterns for Dividing Authentication and Authorization

There's design latitude in "what and how much to do at the Gateway." This is the comparison I most want to convey in this article.

PatternGateway's responsibilityService's responsibilitySuitable cases
A. Centralize authentication onlyVerify token signature, expiry, and issuer. Propagate the verified information to services via headersAll of authorization (can this user touch this resource)When authorization logic depends deeply on the domain. The most common
B. Centralize coarse-grained authorization tooA + a declarative check of "this API path requires this scope/role"Fine-grained authorization (per resource, row level)When you want to govern the exposure scope of APIs. Publishing a B2B API, etc.
C. Centralize all authorizationEvery authorization decisionNone (executes on trust)Only when authorization rules are simple and rarely change. Domain knowledge leaks into the Gateway, so it doesn't scale

The practical equilibrium is A or B. I believe the principle is that authorization should be "coarse-grained at the edge, fine-grained close to the domain." A judgment like "only the order's owner or an admin can edit this order" is knowledge of the order domain, and putting it in the Gateway turns the Gateway into a graveyard for domain logic.

There's another important principle. Just because the Gateway validated a token doesn't mean internal services may skip validation entirely. The moment an internal path that bypasses the Gateway exists (a debug port, direct service-to-service communication), you become defenseless. This is about the limits of perimeter defense, and it's exactly why the idea of the zero trust network—not treating network location as grounds for trust—should apply inside microservices too. At minimum, it's healthy to keep a "defense in depth" in which services also verify the signed headers or JWTs the Gateway attaches.

The Phantom Token Pattern

The trade-off described in Part 1 between JWTs (can't be revoked, contents are visible) and reference tokens (round trips to the AS are heavy) can be resolved to get the best of both—assuming the Gateway exists—using the Phantom Token pattern.

  • Only a reference-type (opaque) token is handed to the outside → contents don't leak, and it can be revoked instantly.
  • Internally, the Gateway swaps it for a JWT and passes it along → each service only needs local signature verification, and internal claims (employee classification, tenant ID, etc.) can be carried safely.

You can satisfy instant revocation, information confidentiality, and validation performance simultaneously—at the cost of making the Gateway and introspection mandatory infrastructure. I think it's a pattern well worth remembering as an improvement path from a "setup that hands JWTs directly to the outside."

Placement of the Gateway and AS — Direction of Dependency and Separation of Paths

When introducing a Gateway, one thing that surprisingly often proceeds without being designed is "where on the network to place the AS." Naively you'd want to think "the AS is a kind of API too, so put it behind the Gateway to govern it," but I consider this an anti-pattern. The general form is "you may share the edge (WAF/LB/CDN), but don't place the AS under the Gateway," separating the paths by hostname, such as auth.example.com (direct to the AS) and api.example.com (via the Gateway). There are three reasons.

  1. To keep the direction of dependency one-way. The Gateway depends on the AS (fetching JWKS, introspection). Placing the AS under the Gateway creates a circular dependency: a Gateway failure drags down even token issuance, deployment and startup order become entangled, and isolating failures becomes hard. The healthy form is a one-way dependency: "the Gateway can't function without the AS, but the AS is self-contained without the Gateway."
  2. Because the availability class differs. The AS is the precondition for login and token issuance across all services, and requires the highest availability in the entire system. If you put it on the same infrastructure and the same deployment pipeline as the Gateway, the Gateway's change risk becomes directly tied to the AS's availability.
  3. Because the nature of traffic differs. The Gateway's policies (token validation, scope checks) are aimed at "API calls that hold a token," but what arrives at the AS's authorization endpoint is a browser that doesn't hold a token yet. The defense needed here is not token validation but WAF, bot protection, and rate limiting of login attempts (credential-stuffing countermeasures)—the way you defend is fundamentally different.

Furthermore, within the AS itself, it's standard to separate exposure per endpoint. It's the network version of the principle of least privilege: there's no reason to expose things that differ in "who calls them" on the same path.

EndpointExposureReason
/authorize, /token, /jwks, /.well-knownPublic (behind WAF)Called directly by clients and browsers
introspection, Token ExchangeInternal network onlyCalled only by the Gateway and RS. No reason to expose externally
Management API (client registration, etc.)A completely separate system as a management planeIf compromised, trust in all clients collapses

In domains requiring high assurance levels, you sometimes go as far as placing the signing key in an HSM/KMS, carving out only the signing operation, so the AS process itself never touches the plaintext private key. Whether you can build "even if the AS leaks, the key doesn't" changes the blast radius during an incident by a full notch.

Splitting the Gateway into External and Internal

Taking the placement discussion one step further, a configuration that "splits the Gateway itself into external-facing and internal-facing" is also often adopted. It looks redundant at first glance, but it clicks once you realize that what's lumped together as "the Gateway" actually mixes three kinds of demand, and the split is the act of untangling them.

External GatewayInternal GatewayService-to-service (east-west)
ClientPartners, public API consumers, mobile appsInternal apps, employee tools, other departmentsServices with each other
Threat modelHarsh. WAF, bot protection, vetted clients onlyMilder. Combined with internal IdP and network controlsImpersonation, eavesdropping (covered in Stage 3)
Change frequencyLow. Public APIs are compatibility contracts, so you can't touch them carelesslyHigh. Internal APIs grow daily—
TokenThe conversion point for opaque (external)/JWT (internal) (Phantom Token)JWT as-isService mesh + workload ID
Governance styleReview and approval (gatekeeper type)Guardrail type, speed firstAutomated on the platform

Of these, the current mainstream view is that east-west shouldn't go through the Gateway at all. Routing all service-to-service communication through a central box is poor on both latency and single-point-of-failure grounds; leave that to the mesh (or a shared library for token validation), and limit the Gateway to north-south (the flow from outside to inside).

The value of splitting the remaining north-south into external/internal is isomorphic to the AS-placement discussion just above: don't put things with different threat models, change frequencies, and ownership into the same box. A co-located setup where the daily miscellaneous changes to internal APIs become change risk for externally exposed infrastructure is bad blast-radius design. You gain independence: a failure or change accident in the external Gateway doesn't stop internal business, and vice versa. Organizationally too, it's easy to split ownership—the external Gateway to the security or API-product team, the internal Gateway to the platform team—and tailor the governance style (gatekeeper vs. guardrail) to each.

The trade-off is straightforward: operational duplication (duplicated products, monitoring, and policy definitions), and confusion over "which one should this API go on." The latter is mostly preventable by narrowing the decision criterion to a single point—"is the client outside the organization or not"—and putting it in writing. If you make the criterion a matter of degree like "importance" or "confidentiality," disputes about which side to place things on become the norm.

Adoption can also be staged. While services are few, keep them logically separated with different listeners/virtual hosts on a single Gateway; physically separate them once external exposure becomes serious—when a vetting process for external clients starts running and change management for public APIs falls into a rhythm distinct from internal. That, I think, is a realistic migration line.

Stage 3: AuthN/AuthZ Between Microservices

The Gateway lets us govern the entrance. The next problem is the inside. When Service A calls Service B, on what basis does B permit it?

Challenge 1: Propagating User Context and the Confused Deputy

If Service A hands the user's token straight to B, the audience won't match (B would accept a token originally issued for A). And if instead A calls B with its own privileges (which tend to be strong), then A performs, on the user's behalf, an operation that the user shouldn't actually be allowed—the classic Confused Deputy problem. The deeper the call chain, the more "under whose privileges is this processing running right now" gets lost.

The standard solution to this is Token Exchange (RFC 8693). Service A presents its token to the AS and swaps it for a new token that is "limited to destination B (aud=B), with only the necessary scopes" before making the call.

You can narrow privileges per destination while preserving the user's identity (sub). You could call it a mechanism that applies the principle of least privilege to the service call chain. The cost is increased AS calls and latency; whether you dutifully exchange at every hop or only when crossing a trust boundary is a discussion with performance.

Challenge 2: The Service's Own Identity

In batch or asynchronous processing where no user is involved, the service itself becomes the principal. The options are:

MethodMechanismCharacteristics
Client Credentials flowThe service obtains a token from the AS as an OAuth clientSelf-contained within OAuth. Distribution and rotation of credentials (secret/private key) is the challenge
mTLSMutual TLS. Identifies services by certificateRobust, but certificate lifecycle management is heavy
Service mesh + SPIFFE/SPIREA sidecar automatically issues and rotates a workload ID (SVID)Solves mTLS's operational problems by platformizing them. Adoption itself is a major undertaking

In practice these are not mutually exclusive; they are used together as layers. The combination "prevent service impersonation with the mesh's mTLS (transport layer), and carry user context and privileges with OAuth tokens (application layer)" is, I believe, one endpoint for large-scale configurations. Transport-layer authentication can't carry the user's privileges, and tokens can't prevent impersonation of the network path. They protect different things.

Challenge 3: Where to Put Authorization Policy — The PDP/PEP Model

When authorization decisions scatter across services, the governance problem arises again. Against this, there's an architecture that separates policy decision (PDP: Policy Decision Point) from enforcement (PEP: Policy Enforcement Point). A representative setup places OPA (Open Policy Agent) as a sidecar, centrally managing policies in Rego while making the decision itself locally in each Pod. If you want to express authorization by the relationships between resources, the ReBAC family descending from Google's Zanzibar (SpiceDB, OpenFGA) becomes an option.

That said, honestly, I have a feeling that not many organizations truly need centralized policy management. In the majority of cases, "scattered authorization logic" can be sufficiently solved by "a shared library + standardizing scope design" rather than introducing a policy engine, and introducing a PDP is an investment that pays off in regulated industries with strict audit requirements, or in multi-tenant SaaS where cross-tenant leakage is fatal.

Organizational Design and the Trade-offs of Governance

This may be the section I most wanted to write in this series. The technical choices of Stages 2–3 are, in fact, almost entirely a matter of organizational theory.

The Workings of Conway's Law

The API Gateway and identity platform become, on the org chart, the possessions of the "platform team." As a result:

  • The benefits for the governing side: You can enforce security policy, audit logging, and rate limiting at a single point. You have the evidence trail for compliance (SOC2, PCI DSS). You can cut things off at a single point during an incident.
  • The cost to the autonomous side: Each service team's deployments and changes end up waiting on the Gateway team's work. Once you're in a state like "one week to add a single route," the true value of microservices (team independence) is killed.

In other words, the Gateway can be an organizational chokepoint at the same time as it is a technical chokepoint. The standard ways to mitigate this are:

  1. Self-service: Turn routing and client registration into GitOps, and shift the platform team to "review and providing guardrails." Declarative configuration + policy checks in CI (e.g., mechanically enforcing "adding a route without authentication requires the security team's approval").
  2. Guardrail-type governance: Rather than "making everything approval-based," "detect and stop only dangerous changes." The idea is to turn governance from a gatekeeper into rails.
  3. Splitting the Gateway: Split the Gateway per business domain, and have the center distribute only common policies (authentication, auditing) as a shared module. A "single giant Gateway" tends to become both a single point of failure and a site of political contention.

Which Is Right, Centralization or Decentralization

My view is that correctness depends on the organization's phase.

  • Domains with strong regulation / large incident impact (finance, healthcare) → lean toward central governance. The sacrifice in development speed is paid as an insurance premium.
  • Domains where speed is the lifeline in a competitive environment → lean toward decentralization, and secure governance through "observation (logs, detection)." From pre-emptive governance to after-the-fact detection.
  • What's dangerous is the unaware middle—that is, a state of "governance in name only, with no actual effectiveness." Once every team finds Gateway applications a nuisance and starts building bypass routes (direct exposure, tunneling), governance has become a hollow formality. Governance functions only when the cost of complying with it is lower than the cost of circumventing it.

Summary of the Difficulty of Stages 2–3

TransitionTechnical difficultyOrganizational difficultyPrerequisites
Gateway adoption (centralizing authentication)MediumMediumA stably operating IdP
Phantom TokenMediumLowGateway + an introspection-capable AS
Token ExchangeMedium–HighMediumAS support (supported products are still limited), modifications on the service side
Service mesh + workload IDHighHighA K8s foundation and a mature platform team
PDP/PEP separation (OPA, etc.)HighHighA structure to keep writing and operating policies. Half-baked adoption becomes debt

Organizations that need the full set of Stage 3 are a minority, and many organizations can fight for a long time with "Gateway + Pattern A/B + Client Credentials." There is no value in adopting a high-difficulty pattern for its own sake; I believe the healthy approach is to add incrementally once the problem actually surfaces.


In Part 3 (Advancing Client Authentication and Incremental Migration), we shift our perspective to the client side and cover the essential weaknesses of Bearer tokens, PKCE, private_key_jwt, mTLS, sender-constraint via DPoP, the BFF pattern, and the incremental migration strategy running through the whole series.