The Gateway Does Not Know Whose Order That Is: API Management and Delivering a Service
Here is a failure common enough to be worth stating as a pattern. A platform team puts a gateway in front of a few dozen services and announces that authentication and authorization are now handled centrally. Some months later a penetration test finds that any authenticated caller can read any customer’s invoice by changing an identifier in the URL. The gateway checked the token on every request, exactly as promised. It had no way to know which invoices belonged to whom.
That gap is the subject of this article. A gateway is genuinely useful and it can only enforce what it can evaluate from the request — so the division of labour has to be explicit, or each side assumes the other did the check. What follows is what the gateway does, where its authority ends, how quotas and rate limits behave, and what else it takes to deliver an API as something other teams can actually adopt. Standards quoted are RFC 6750 and RFC 6585; product structure follows the Azure API Management documentation, checked in September 2026.
What the gateway actually does
Managed API platforms are usually described as three parts, and the split is worth keeping in mind because people say “the gateway” when they mean all three: “an API gateway, a management plane, and a developer portal.” The API gateway is the request path; the management plane is where providers configure things; the portal is where consumers discover and subscribe.
The documented responsibilities of the gateway itself are a useful checklist of what you get. It “acts as a facade to backend services by accepting API calls and routing them to appropriate backends,” “verifies API keys and other credentials such as JWTs and certificates presented with requests,” “enforces usage quotas and rate limits,” optionally “transforms requests and responses as specified in policy statements,” optionally “caches responses,” and “emits logs, metrics, and traces.”
Read that list for what it has in common: every item is decidable from the request, the caller’s identity, and counters the gateway keeps. Nothing on it requires knowing your data. That is the line, and the rest of this section is about where it falls.
| Question | Who can answer it |
|---|---|
| Is this caller who they claim to be? | Gateway. Signature, issuer, expiry — all present in the request |
| Is this caller allowed to use this operation at all? | Gateway, from the token’s scopes or the product subscription |
| Has this caller exceeded what they may consume? | Gateway. It keeps the counters |
| Is the request well formed? | Gateway for schema and size; the service for anything semantic |
| May this caller see this record? | The service, by default and by preference. The answer depends on who owns the invoice and what state it is in. A gateway can be made to answer it — with a trusted tenant claim, or by calling out to an authorization service — but it is then holding a copy of your domain rules at the edge |
| Is this operation valid in the current business state? | The service. Whether an order can still be cancelled depends on state the gateway would have to fetch, and checking it anywhere but where the change commits leaves a race |
Two failures follow from leaving that table implicit, and they arrive from opposite directions. Services skip their own checks because “the gateway handles security,” which produces the penetration-test finding above. And gateways accumulate business logic — a policy that rewrites a field, then one that branches on a customer tier — until a material part of the domain lives in the request path. The second is more common in mature installations and harder to unwind, and the reason is ownership rather than tooling: policy configuration can be version-controlled, reviewed, and tested, but in practice it is often edited in a portal by whoever is on hand, while the service repository has a code owner and a pipeline.
The rule that holds: the gateway is a policy enforcement point for things decidable at the edge, and every service still validates its own inputs and authorizes its own data access. That is not redundant work; the two are answering different questions.
Tokens: what the edge can check
An access token is what a caller presents instead of credentials. RFC 6749 defines it as “a string representing an access authorization issued to the client,” and RFC 6750 describes the flow plainly: “tokens are issued to clients by an authorization server with the approval of the resource owner. The client uses the access token to access the protected resources hosted by the resource server.”
Note the three parties. The authorization server issues, the client presents, and the resource server — your service, or the gateway in front of it — decides whether to accept. Five checks are available at that point, and all five are edge work.
- Signature, against the issuer’s published keys. Reading a token’s contents without verifying them is how a forged one gets accepted. (This assumes a self-contained signed token, which is what the rest of this section is about — see the note below for the other kind.)
- Issuer, so a token minted by a different authority is rejected rather than trusted for looking similar.
- Audience. The most commonly skipped check, and the one that turns a token issued for another service into a valid credential for yours.
- Expiry, with whatever clock tolerance you have decided and written down.
- Scope, which is coarse permission — what class of operation the client may attempt.
The standard’s error vocabulary shows what a plain scope check can and cannot say. A failure produces “the appropriate HTTP status code (typically, 400, 401, 403, or 405)” with an error code: invalid_token when the token “is expired, revoked, malformed, or invalid for other reasons,” answered with 401, after which “the client MAY request a new access token and retry the protected resource request.” And insufficient_scope when “the request requires higher privileges than provided by the access token,” answered with 403 and optionally naming “the scope necessary to access the protected resource.”
Those two cover authentication and coarse permission. Notice that OAuth defines no error code for “you may read invoices, but not this one” — scopes describe classes of operation, not individual objects. That is a gap in the vocabulary, not a proof about where the decision has to be made: a per-object refusal is an ordinary 403 or 404 with your own error body. The distinction between authentication and authorization is the same one, and the second half splits again into what a scope can express and what depends on your data.
Three practical notes. Scopes should be granted per client and kept narrow — the same least privilege reasoning as anywhere, and a token carrying every scope because it was easier is a credential whose theft is maximally useful.
The five checks above describe a signed, self-contained bearer token, which is the common case and not the only one. An OAuth access token may instead be a reference that carries no claims at all, in which case the resource server validates it by asking the authorization server rather than by checking a signature — the specification allows both, saying a token “may denote an identifier used to retrieve the authorization information or may self-contain the authorization information in a verifiable manner.” Tokens can also be bound to a client key so that possession alone is not enough to use them. So treat the list as the procedure for the token type you have chosen, stated in your own documentation, rather than as what every token requires.
And get the unauthenticated response right, because the standard is more specific than it is usually implemented. When a request carries no credentials the resource server must still send a challenge: it “MUST include the HTTP ‘WWW-Authenticate’ response header field,” and “all challenges defined by this specification MUST use the auth-scheme value ‘Bearer’.” What is omitted in that case is the diagnostic detail — with no authentication information present the server “SHOULD NOT include an error code or other error information.” The specification’s own example is 401 Unauthorized with WWW-Authenticate: Bearer realm="example": a header that says how to authenticate, without saying what was wrong. A bare 401 with no header is not the specified behaviour, and it leaves a new consumer with nothing to go on.
Quotas and rate limits are two different things
A managed gateway “enforces usage quotas and rate limits,” and the pairing hides a distinction worth making explicit, because the two have different owners and different responses when hit.
| Quota | Protective rate limit | |
|---|---|---|
| Purpose | Defines what a consumer bought or was allocated | Keeps the service standing |
| Set by | The product or commercial agreement | Engineering, from measured capacity |
| Period | Long — a day, a month | Short — a second, a minute |
| When exceeded | A business event: the consumer needs a bigger plan | An operational event: something is wrong, or traffic grew |
| Who should be told | The consumer, in advance of hitting it | You, immediately — a limit being hit regularly is a capacity signal |
Both are forms of throttling, and for a protective limit the status code is 429: “the 429 status code indicates that the user has sent too many requests in a given amount of time (‘rate limiting’).” The specification asks for two things in that response — representations “SHOULD include details explaining the condition,” and the response “MAY include a Retry-After header indicating how long to wait before making a new request.” Include both. A 429 without a wait hint produces clients that retry immediately and make the situation worse, which is the practical content of Retry-After and backoff.
Do not assume a quota breach answers the same way, because a widely used product does not. In Azure API Management the rate-limit policy is as expected — “when the call rate is exceeded, the caller receives a 429 Too Many Requests response status code” — but the quota policy is different: “when the quota is exceeded, the caller receives a 403 Forbidden response status code, and the response includes a Retry-After header whose value is the recommended retry interval in seconds.” A consumer that treats 403 as an authorization failure will log a credentials problem for what is an ordinary allowance breach. Whatever you choose, write down which code each condition returns, and check what your gateway actually sends rather than what seems natural.
RFC 6585 also declines to specify the hard part: “this specification does not define how the origin server identifies the user, nor how it counts requests.” Those two choices are yours and they determine whether the limit is fair.
- What identifies the caller. A subscription key, a client identity from the token, a source address. An address groups unrelated callers behind one office network and splits one caller across many containers, so it is the weakest of the three.
- How requests are counted. A fixed window lets a caller spend its whole allowance in the last second of one window and again in the first second of the next — double the intended rate at the boundary. A sliding window or a token bucket avoids that, and a bucket also expresses “a burst is fine, sustained load is not,” which is usually what you mean.
- Where the count is kept. This is the one that surprises people, because a configured number reads like a global ceiling and often is not. Azure API Management states it plainly: the policy “tracks calls independently at each gateway where it is applied, including workspace gateways and regional gateways in a multi-region deployment,” and “it doesn’t aggregate call data across the entire instance.” Set 100 calls per caller and deploy to three regions and a caller can make 300. The same documentation is candid about precision even within one gateway — “because of the distributed nature of throttling architecture, rate limiting is never completely accurate.” Approximate enforcement is fine for protecting a service and unacceptable for a contractual allowance you bill against, which needs a single counted store.
- What counts as one request. If one call can ask for ten thousand records, counting calls limits nothing that matters. Weight by cost, or limit the expensive parameter separately.
There is also a choice about behaviour at the limit that is usually made by default rather than deliberately. Rejecting is honest and immediate. Queueing is kinder to the caller and dangerous for you, since a queue is a place latency accumulates and a way to run out of memory — which is the backpressure question rather than a gateway setting. Degrading — serving a cached or reduced response — is often the best answer for reads and needs the service’s cooperation.
Whatever you choose, consumers need to be able to see where they stand before they hit it. A limit discoverable only by tripping it turns a documented constraint into an incident, and the usual fix is a small header with the remaining allowance plus per-consumer figures in the portal.
Publishing is not the same as delivering
An API that works and that nobody can start using has not been delivered. The consumer-facing half of a managed platform is the developer portal, and the documented list of what consumers do there is a reasonable specification for the job even if you build it yourself: “read API documentation,” “call an API via the interactive console,” “create an account and subscribe to get API keys,” “access analytics on their own usage,” “download API definitions,” and “manage API keys.”
Two of those deserve emphasis because they are the ones most often missing. An interactive console collapses the distance between reading and trying, which is where most abandonment happens. And self-service subscription decides whether onboarding takes two hours or two weeks — the platform model here is products, which are “how APIs are surfaced to API consumers,” with protected products requiring a subscription key and approval that “can either require an administrator’s approval or be automatic.” Choosing automatic for internal consumers is the difference between a platform and a queue.
The standard I would hold a portal to: a developer who has never seen the API makes a successful authenticated call within two hours, without talking to anyone. That is testable — have someone do it — and most portals fail it on a missing credential step rather than on documentation.
Beyond the portal, four things make an API a supported product rather than an artifact.
- A named owner and a way to reach them. Not a shared inbox nobody watches.
- A stated support level — what consumers may expect of availability and response, and during which hours. Silence here means every consumer assumes the strongest interpretation.
- A change communication channel that reaches the people who integrated, which is rarely the same list as the people who subscribed.
- Per-consumer usage visibility — for you, so deprecation is possible at all, and for them, so they can see their own behaviour.
Treating the API surface this way — documented, self-service, owned, with a stated support expectation — is platform engineering applied to interfaces, and the portal is where the golden path for a consuming team either exists or does not.
What a gateway should not become
Policies are powerful in a way that invites trouble: they are “a collection of statements that are executed sequentially on the request or response of an API,” applied at “global (all APIs), a workspace, a product, a specific API, or an API operation.” That is a lot of places for behaviour to live, and configuration is easier to change than code — which is exactly why things migrate into it.
Three things belong on the other side of the line.
- Business rules. A policy that decides eligibility, computes a price, or branches on a customer tier is application logic living outside the service that owns the domain — so the rule now has two homes and can disagree with itself. The tell is a policy that would need updating when a business rule changes.
- Response reshaping that hides a bad interface. Transformation is a documented capability and it is right for protocol-level work; using it to paper over a backend that returns the wrong shape means two artifacts must now be understood together, and the backend never gets fixed.
- Orchestration without an owner. Combining three backend calls at the gateway is a documented pattern, not a mistake in itself — it saves a chatty client several round trips, and Microsoft describes exactly that as gateway aggregation. What makes it a liability is doing it without the things a service gets by default: a name, a team, tests, and a deployment of its own. Left unowned, it is logic in the request path of everything sharing that gateway and nobody’s to fix. Two conditions push it out into a separate service regardless of ownership — resource demands or failure behaviour that you do not want shared with other traffic, and a need to deploy or roll it back independently. And a simple read aggregation is a different proposition from long-running business orchestration, which wants durable state and belongs in a service either way.
None of that is an argument that policy cannot be engineered properly — it can. Policies are text, so they belong in version control and in a pipeline, and vendors ship tooling for testing them: Azure’s policy toolkit lets policies be authored as code in a solution with a companion testing package, so expressions are covered by ordinary unit tests. The risk is organizational rather than technical, and it is worth naming as such. Ask who owns this policy, where it is reviewed, and what test fails if it is wrong; when the answers exist, a policy is a legitimate place for edge concerns. When they do not — when the honest answer is that someone edited it in a portal during an incident — that is the condition under which logic at the edge becomes unmaintainable, and it is a condition you can fix.
Two practices follow. Keep policies in version control with an owner and tests, and review them like code, since the gateway configuration is part of the running system whatever tool edits it. And use the observability the gateway already provides — it “emits logs, metrics, and traces” — as the record of who called what, which is the raw material for both capacity decisions and the audit log a security review will ask for.
A workable division of labour
Stated as a short contract between the platform team and the service teams, the split that survives audits looks like this.
- The gateway terminates TLS, authenticates the caller, and rejects anything whose token fails signature, issuer, audience, expiry, or required scope. Uniformly, so no service implements it differently.
- The gateway enforces quotas and protective limits and returns the documented status for each — a rate limit as 429, a quota breach as whatever your platform sends — always with a wait hint.
- The gateway passes the verified caller identity to the service in a form the service can trust, and the service treats a request arriving without it as unauthenticated rather than assuming it came through the front door.
- The service authorizes every data access against that identity, every time, as if there were no gateway.
- The service validates its own inputs beyond schema, and owns every business rule.
Step three is the one to check in an existing estate, because it is where the two halves are joined by an assumption. If a service can be reached directly — from inside the network, by a developer with a port-forward, by another service — then a caller who never passed the gateway gets whatever the service grants to callers it believes were authenticated. Network controls narrow that, and the service verifying the identity itself is what closes it.
One closing judgment. A gateway pays for itself on the uniform, boring work — one place for credential verification, one place for limits, one place where traffic is observable — and it disappoints whenever it is bought as a substitute for services being correct. The most useful sentence a platform team can publish alongside it is the second column of that first table: here is what we check, and here is what remains yours.
References: RFC 6750, The OAuth 2.0 Authorization Framework: Bearer Token Usage; RFC 6585, Additional HTTP Status Codes; Microsoft Learn, Azure API Management: Overview and key concepts (documentation dated 2025-10-13); Microsoft Learn, Gateway Aggregation pattern; RFC 9396, OAuth 2.0 Rich Authorization Requests (all checked September 2026).
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
