Azure API Management policies are the tiny XML bouncers standing between your consumers and your backend APIs. When they are designed well, they validate tokens, smooth traffic spikes, keep browser clients honest, and stop secrets from turning into confetti. When they are designed casually… well, congratulations, you have invented a very expensive request forwarding service.
This guide walks through seven practical Azure API Management policy guardrails that admins, platform engineers, and developers can use to make APIs safer and easier to operate. The goal is not to replace good backend security. The goal is to make the gateway do gateway things brilliantly, so your apps can focus on the fun work.
Quick take
The safe API policy stack
Start with identity, add traffic controls, lock down cross-origin browser access, preserve inherited policies with <base />, and treat managed identity tokens like hot potatoes: useful, powerful, and not something you casually toss across the room.
Where Azure API Management policies fit
Azure API Management policies run in the gateway between the caller and your backend API. Microsoft documents the main policy sections as inbound, backend, outbound, and on-error. In plain English:
- Inbound is where you usually validate, normalize, throttle, route, and authenticate before the backend sees the request.
- Backend controls behavior while forwarding to the backend.
- Outbound shapes the response before it returns to the caller.
- On-error gives you a controlled place to handle gateway-side errors.
App / user / service
JWT, CORS, throttle
Route / auth
App / function / service
The powerful bit: these policies can change runtime behavior without rebuilding every backend. The dangerous bit: a broad global policy can affect every API. That is why guardrails matter.
Guardrail 1: Preserve inherited policies with <base />
API Management policies can be configured at multiple scopes: global, workspace, product, API, and operation. Microsoft recommends including a <base /> element at the beginning of each policy section so child scopes inherit policies from parent scopes.
This is one of those small things that prevents a large future headache. Without <base />, an operation-level edit can unintentionally bypass a global control. That is how a beautifully governed API estate becomes a raccoon with admin rights.
<policies>
<inbound>
<base />
<!-- Operation-specific policies go here -->
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Admin habit: when troubleshooting an API that mysteriously ignores a policy, use the portal’s effective policy view and confirm inheritance is not being skipped.
Guardrail 2: Validate tokens before the backend does expensive work
The validate-jwt policy enforces the existence and validity of a JSON Web Token from a header, query parameter, or explicit token value. Microsoft also notes that for Microsoft Entra tokens, the purpose-built validate-azure-ad-token policy is available. Either way, the design principle is the same: reject bad identity signals early.
A practical inbound policy should usually check:
- Issuer — who issued the token?
- Audience — was the token meant for this API?
- Expiration — is the token still valid?
- Required claims — does the caller have the expected role, scope, app ID, or tenant?
- Scheme — if using the Authorization header, require the expected
Bearerscheme.
<validate-jwt header-name="Authorization"
require-scheme="Bearer"
failed-validation-httpcode="401"
failed-validation-error-message="Invalid or missing access token">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://your-api-app-id</audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/{tenant-id}/v2.0</issuer>
</issuers>
</validate-jwt>
Guardrail 3: Rate-limit by the right key
The rate-limit-by-key policy limits calls per key for a configured renewal period. It returns 429 Too Many Requests when the configured rate is exceeded. That makes it great for stopping one noisy caller from flattening the shared backend buffet.
The key design choice is which key to use:
| Counter key | Good for | Watch out for |
|---|---|---|
context.Request.IpAddress |
Simple public APIs, anonymous calls, quick abuse controls | NAT gateways can make many users look like one caller |
| Subscription ID | Developer portal/product-based APIs | Only helps where subscription keys are part of the design |
| JWT claim | Per-app, per-user, or per-tenant controls | Validate the token before trusting claims |
<rate-limit-by-key calls="120"
renewal-period="60"
counter-key="@(context.Request.IpAddress)"
remaining-calls-header-name="x-ratelimit-remaining"
retry-after-header-name="Retry-After" />
Microsoft cautions that rate limiting is not perfectly accurate because throttling is distributed. Treat it as a strong operational control, not a mathematically perfect invoice meter.
Short window. Stop spikes now.
Longer allowance. Control consumption over time.
Limit simultaneous work when a backend is fragile.
Guardrail 4: Make CORS intentionally boring
CORS should not be exciting. If CORS is exciting, someone is probably debugging a production browser error while muttering at an OPTIONS request.
The API Management cors policy lets the gateway handle cross-origin browser access. Microsoft’s CORS policy reference calls out several common issues, including policy order, header-based subscription keys at product scope, and the risk of overly permissive wildcard settings.
Keep it boring:
- Put
corsearly in the inbound section where Microsoft’s guidance expects it. - Avoid
*unless the API is genuinely intended for any origin. - List explicit origins for portals, SPAs, and partner apps.
- Allow only the methods and headers the browser client needs.
- Test preflight requests separately from normal GET/POST calls.
<cors allow-credentials="false" terminate-unmatched-request="true">
<allowed-origins>
<origin>https://apps.contoso.com</origin>
</allowed-origins>
<allowed-methods preflight-result-max-age="300">
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>authorization</header>
<header>content-type</header>
</allowed-headers>
</cors>
Guardrail 5: Use managed identity carefully for backend calls
The authentication-managed-identity policy lets API Management obtain an access token from Microsoft Entra ID for a target resource and set it on the Authorization header using the Bearer scheme. Microsoft documents examples for resources such as Azure Resource Manager, Key Vault, Azure OpenAI, Storage, Service Bus, Event Hubs, and custom app IDs.
This is excellent for removing hardcoded secrets from gateway-to-backend calls. It also deserves careful scoping. Microsoft’s security note is worth taking seriously: users who can edit API Management policies may be able to use the managed identity token in ways you did not intend if permissions and policies are too broad.
<authentication-managed-identity resource="https://vault.azure.net" />
Design it like this:
- Assign the managed identity only the permissions the backend call requires.
- Prefer API or operation scope instead of global scope when only one API needs the token.
- Review who can edit policies, not just who can deploy APIs.
- Use backend entities and route controls so tokens go only to trusted services.
If you recently read our App Service Key Vault References troubleshooting guide, the same theme applies here: managed identity is magic until the permissions, network path, or scope are slightly wrong. Then it is still magic, just the haunted kind.
Guardrail 6: Add response headers on purpose
The set-header policy can add, override, append, or delete headers in different policy sections. It is useful for correlation, security headers, deprecation warnings, and rate-limit hints.
Examples worth considering:
x-correlation-idso callers can include an identifier in support tickets.x-api-versionorDeprecationstyle hints when migrating clients.- Security headers for browser-facing APIs where appropriate.
- Remaining-call headers from rate limiting so well-behaved clients can back off before they get bonked with a 429.
<set-header name="x-gateway-region" exists-action="override">
<value>@(context.Deployment.Region)</value>
</set-header>
Microsoft notes that some headers cannot be overridden, appended, or deleted, including Connection, Content-Length, Keep-Alive, and Transfer-Encoding. So if your header policy appears to be politely ignoring you, check the limitations before blaming the nearest intern.
Guardrail 7: Build a troubleshooting checklist before the outage
When API Management policy issues appear, they usually fall into a few repeatable buckets: inheritance, order, identity, throttling keys, CORS preflight behavior, or backend routing. A checklist beats vibes every time.
For broader Azure guardrail hunting, pair this with Azure Resource Graph queries for admin guardrails. API policies protect runtime behavior; Resource Graph helps you spot configuration drift across the estate. Together, they make a nice little admin utility belt.
A practical baseline policy pattern
Here is a simplified pattern for a secured API that uses inherited controls, token validation, CORS, rate limiting, and headers. Adapt values to your tenant, API app registration, and client applications.
<policies>
<inbound>
<base />
<cors allow-credentials="false" terminate-unmatched-request="true">
<allowed-origins>
<origin>https://apps.contoso.com</origin>
</allowed-origins>
<allowed-methods preflight-result-max-age="300">
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>authorization</header>
<header>content-type</header>
</allowed-headers>
</cors>
<validate-jwt header-name="Authorization"
require-scheme="Bearer"
failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://your-api-app-id</audience>
</audiences>
</validate-jwt>
<rate-limit-by-key calls="120"
renewal-period="60"
counter-key="@(context.Request.IpAddress)"
remaining-calls-header-name="x-ratelimit-remaining" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="x-gateway-region" exists-action="override">
<value>@(context.Deployment.Region)</value>
</set-header>
</outbound>
<on-error>
<base />
</on-error>
</policies>
- Inventory current global, product, API, and operation policies.
- Stage changes in a non-production API or test operation.
- Validate auth, CORS, throttling, and backend routing with real client patterns.
- Deploy narrowly first, then expand scope when behavior is proven.
- Monitor 401, 403, 429, and backend error trends after rollout.
Final thoughts
Azure API Management policies are one of the fastest ways to add repeatable runtime guardrails to APIs without rewriting every backend. The trick is to be deliberate: inherit parent controls, validate identity early, limit noisy callers, keep CORS narrow, treat managed identity as privileged, and make troubleshooting observable.
Do that, and your gateway becomes a helpful platform control instead of a mysterious XML cave. Still a cave, technically. But with labels, lighting, and fewer bats.
Sources
- Microsoft Learn: Policies in Azure API Management
- Microsoft Learn: Azure API Management policy reference
- Microsoft Learn: validate-jwt policy reference
- Microsoft Learn: rate-limit-by-key policy reference
- Microsoft Learn: cors policy reference
- Microsoft Learn: authentication-managed-identity policy reference
- Microsoft Learn: set-header policy reference
- Microsoft Learn: Mitigate OWASP API security threats with Azure API Management
Discover more from SharePoint Monkey
Subscribe to get the latest posts sent to your email.