This chapter covers Azure API Management (APIM) security policies, a critical component for securing APIs in Azure. For the AZ-500 exam, understanding APIM policies is essential for controlling authentication, authorization, rate limiting, and request/response transformation. Approximately 10-15% of exam questions touch on API Management security, making it a significant topic within the Network Security domain. This chapter will provide the deep technical knowledge needed to configure and troubleshoot APIM security policies effectively.
Jump to a section
Imagine a medieval palace with many internal departments (backend APIs). The palace has a single main gate (API Management gateway) where all visitors (client apps) must present their credentials. The gatekeeper (APIM) checks each visitor's ID (authentication), verifies their purpose (authorization), and records their entry in a log (audit). If a visitor is allowed, the gatekeeper announces them to the correct department (backend API) and relays messages back and forth. The gatekeeper can also limit how many visitors enter per hour (rate limiting), inspect packages for weapons (validate requests), and transform messages from one language to another (policy transformation). Importantly, the gatekeeper never lets visitors wander inside the palace — they only interact through the gatekeeper. This protects the internal departments from direct exposure and ensures consistent security policies are applied to all external access.
What is Azure API Management and Why Security Policies Matter
Azure API Management (APIM) is a fully managed service that enables you to publish, secure, transform, maintain, and monitor APIs. It acts as a facade between your backend APIs and external consumers. Security policies in APIM are XML-based rules that are executed in a defined order as requests and responses flow through the gateway. These policies allow you to enforce authentication, authorization, rate limiting, IP filtering, CORS, and more without modifying your backend code. The AZ-500 exam focuses on how to implement these policies to protect APIs from unauthorized access, abuse, and attacks.
How APIM Security Policies Work Internally
APIM policies are defined in XML and consist of a collection of statements that are executed sequentially. Policies can be applied at three scopes: global (all APIs), product (a group of APIs), or API/operation (individual API or specific operation). The policy execution order is: inbound (on request), backend (to backend service), outbound (on response), and on-error (if an error occurs). Each policy element performs a specific action, such as validating a JWT token, checking IP address, or transforming XML to JSON.
When a request arrives at the APIM gateway, the following sequence occurs: 1. The gateway processes inbound policies (e.g., authentication, rate limiting, IP filtering). 2. If the request passes all inbound policies, it is forwarded to the backend service (with optional backend policies). 3. The backend response is processed by outbound policies (e.g., caching, response transformation). 4. If any policy fails, the on-error section executes, returning an error response.
Key Security Policy Components
Authentication Policies:
- validate-jwt: Validates a JWT token. Key attributes: issuer, audiences, roles, openid-configuration. Default: token must be present unless require-expiration-time is false. Common exam trap: forgetting to specify require-expiration-time (default true).
- basic-authentication: Validates HTTP Basic authentication. Typically used for legacy systems.
- client-certificate: Validates client certificate thumbprint or subject.
Authorization Policies:
- check-header: Checks for the presence and value of a header.
- set-header: Modifies or adds headers.
- limit-concurrency: Limits number of concurrent requests (default 1).
- rate-limit: Limits call rate per key (e.g., per subscription). Default: calls per time period.
- quota: Limits total number of calls per key over a period (e.g., per month).
IP Filtering:
- ip-filter: Allows or denies requests from specific IP addresses or ranges. Syntax: <ip-filter action="allow|deny"> with <address> or <range>.
CORS:
- cors: Enables Cross-Origin Resource Sharing. Must specify allowed origins, methods, and headers. Common mistake: not including Access-Control-Allow-Origin in the response.
Configuration and Verification
Policies are edited in the Azure portal under the API Management instance > APIs > select API > Design tab > Code editor. Example inbound policy to validate JWT:
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<openid-config url="https://login.microsoftonline.com/tenant-id/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud" match="any">
<value>api://client-id</value>
</claim>
</required-claims>
</validate-jwt>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>To verify policies, use the Test tab in the portal to send requests and inspect the trace (HTTP headers and response). You can also use Azure Monitor logs to audit policy executions.
Interaction with Related Technologies
APIM integrates with: - Azure Active Directory: For OAuth2/JWT validation. - Azure Key Vault: To store client secrets and certificates referenced in policies. - Azure Firewall/NSG: For network-level filtering before APIM. - Azure Front Door: For global load balancing and WAF (Web Application Firewall) capabilities. - Azure Policy: To enforce governance on APIM configurations.
Important: APIM security policies are application-layer; they do not replace network security groups or firewalls. They work in tandem with network controls to provide defense in depth.
Common Exam Scenarios
The AZ-500 exam tests your ability to choose the correct policy for a given requirement. For example:
- "You need to restrict access to an API based on client IP addresses." Answer: ip-filter policy.
- "You need to ensure that only requests with a valid JWT from Azure AD are accepted." Answer: validate-jwt policy.
- "You need to limit the number of API calls per subscription to 1000 per day." Answer: quota policy.
- "You need to allow cross-origin requests from a specific domain." Answer: cors policy.
Trap: Candidates often confuse rate-limit (per time window, e.g., 10 calls per minute) with quota (absolute limit over a period, e.g., 1000 calls per month). Also, limit-concurrency limits simultaneous connections, not total calls.
Policy Expressions
APIM policies support C# expressions for dynamic logic. Expressions are enclosed in @(...). Example: @(context.Request.Headers.GetValueOrDefault("Authorization","").AsJwt()?.Claims?.FirstOrDefault(c => c.Type == "role")?.Value). This allows extracting claims from JWT tokens. The exam does not require writing expressions but may test understanding of what expressions can do (e.g., context object properties).
Error Handling
The on-error section is triggered when a policy fails. It can return a custom error response. Example:
<on-error>
<set-variable name="errorMessage" value="@(context.LastError.Message)" />
<return-response>
<set-status code="500" reason="Internal Server Error" />
<set-body>@{
return new JObject(new JProperty("error", context.Variables.GetValueOrDefault<string>("errorMessage"))).ToString();
}</set-body>
</return-response>
</on-error>This is important for exam questions about custom error handling.
Caching and Performance
Policies can also include caching (e.g., cache-store, cache-lookup) to improve performance. Security implications: cached responses might contain sensitive data; ensure cache scope is appropriate (e.g., per user vs. per URL). The cache-lookup-value policy can retrieve cached values like tokens.
Summary of Key Policy Elements
inbound: Request processing.
backend: Backend call.
outbound: Response processing.
on-error: Error handling.
base: Inherits policy from parent scope.
set-variable, set-header, set-body, set-method, set-status, return-response, choose, when.
Exam Tips
Memorize the four policy sections and their order.
Know the difference between rate-limit and quota.
validate-jwt requires openid-config or explicit issuer and audiences.
ip-filter can be placed in inbound or outbound; inbound is typical.
CORS policies must be on the API that handles preflight (OPTIONS) requests.
base element is crucial for inheriting policies; omitting it overrides parent policies.
Always check for require-expiration-time in JWT validation.
Real-World Configuration Example
Scenario: You have an API that should only be accessible from the corporate VPN (IP range 10.0.0.0/8) and requires a valid JWT from Azure AD. Additionally, each subscription can make 1000 calls per day.
Policy:
<policies>
<inbound>
<base />
<ip-filter action="allow">
<address-range from="10.0.0.0" to="10.255.255.255" />
</ip-filter>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" require-expiration-time="true" require-scheme="Bearer">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://{client-id}</audience>
</audiences>
</validate-jwt>
<quota calls="1000" renewal-period="86400" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>Note: quota is placed in inbound to limit before backend call. renewal-period is in seconds (86400 = 1 day).
Define Policy Scope
Determine where the policy applies: global (all APIs), product (group of APIs), or specific API/operation. In the Azure portal, navigate to the API Management instance, select APIs, choose the desired API, and go to the Design tab. Under Inbound processing, click the code editor icon. The scope influences inheritance: policies at lower scopes can override or extend higher-level policies using the `<base />` element. If `<base />` is omitted, the higher-level policy is not inherited. This is a common exam trap: candidates forget that omitting `<base />` breaks inheritance.
Add Inbound Authentication Policy
Insert a `validate-jwt` policy to enforce token-based authentication. Configure the `openid-config` URL to fetch Azure AD public keys. If using a custom issuer, specify `issuer` and `audiences` explicitly. Set `failed-validation-httpcode` to 401 and `failed-validation-error-message` to a custom message. The `require-expiration-time` attribute defaults to true; if the token has no expiration, validation fails. For Basic auth, use `basic-authentication` with a backend validation endpoint. The order of policies matters: IP filtering should come before authentication to avoid unnecessary validation.
Add IP Filtering
Use the `ip-filter` policy to allow or deny IP ranges. Place it before authentication to reject unwanted traffic early. Specify `<address>` for single IPs or `<address-range>` for CIDR ranges. Action can be `allow` (default deny) or `deny` (default allow). The policy checks the client IP from the `X-Forwarded-For` header if behind a load balancer. If APIM is behind Azure Front Door, the original client IP is in `X-Forwarded-For`. This is a common exam scenario: you must configure `ip-filter` to trust the proxy headers using the `forwarded-for` attribute.
Add Rate Limiting or Quota
Decide between `rate-limit` (calls per time window, e.g., 10 per minute) and `quota` (total calls over a period, e.g., 1000 per month). `rate-limit` resets at the end of each window; `quota` resets after the renewal period. Both are keyed by subscription key (or caller key). Place them in inbound to throttle before backend. The `rate-limit` policy uses a sliding window; `quota` uses a fixed window. Exam questions often test the difference: "You need to limit to 1000 calls per month" -> `quota`; "You need to limit to 10 calls per second" -> `rate-limit`.
Configure Outbound and Error Handling
Outbound policies transform responses (e.g., `set-header` to remove sensitive data). The `on-error` section handles policy failures. Use `set-variable` to capture error details and `return-response` to send a custom error body. If no `on-error` is defined, APIM returns a generic 500 error. For compliance, you might log errors to Application Insights. The `base` element in outbound and on-error ensures parent policies are applied. Exam tip: always include `<base />` unless you explicitly want to override.
Enterprise Scenario 1: Banking API with Strict Access Control
A major bank exposes customer account APIs via APIM. Requirement: only requests from the bank's internal IP range (10.0.0.0/8) and with a valid OAuth2 token from Azure AD are allowed. Additionally, each partner application (identified by subscription key) can make at most 10,000 calls per day. The solution: use ip-filter to allow only corporate IPs, validate-jwt to check Azure AD tokens, and quota to limit daily calls. In production, the bank also enables Azure Monitor alerts for quota exceeded events. Misconfiguration: forgetting to include <base /> in the API-level policy caused the global policy (which enforced IP filtering) to be bypassed, allowing external traffic. The fix: ensure <base /> is present in all policy sections.
Enterprise Scenario 2: E-commerce API with CORS and Rate Limiting
An e-commerce company exposes product search APIs to a public web app and a mobile app. The web app runs on https://www.example.com, and the mobile app is untrusted. Requirement: allow CORS from the web app only, and rate-limit mobile app requests to 5 per second. The solution: apply a cors policy at the API scope with allowed origin https://www.example.com. For the mobile app, use a separate subscription key and apply a rate-limit policy of 5 per second. Common pitfall: the cors policy must handle the preflight OPTIONS request; if not, browsers block the request. Also, rate limiting based on IP is unreliable for mobile apps due to NAT; using subscription key is better.
Scenario 3: Healthcare API with JWT and IP Allowlisting
A healthcare provider exposes patient data APIs. Compliance requires that all requests come from a known set of clinic IPs and include a JWT with specific roles (e.g., 'doctor'). The solution: ip-filter with allowed IPs, validate-jwt with required-claims for role claim. Additionally, the API must reject expired tokens; require-expiration-time is set to true (default). The team initially forgot to specify issuer in JWT validation, causing tokens from any issuer to be accepted. The fix: configure openid-config or explicit issuer. Performance consideration: JWT validation fetches keys from the openid-config URL; caching the keys via cache-lookup-value and cache-store-value reduces latency.
What AZ-500 Tests on This Topic
Objective 3.3: "Implement security for API Management." Specifically, the exam tests:
Configuring authentication policies (validate-jwt, basic-authentication, client-certificate).
Configuring authorization policies (check-header, set-header, ip-filter, rate-limit, quota, limit-concurrency).
Understanding policy scopes and inheritance (base element).
Handling CORS and cross-origin requests.
Error handling with on-error.
Common Wrong Answers and Why Candidates Choose Them
Using `rate-limit` when the requirement is a total monthly limit. Candidates confuse rate-limit (sliding window) with quota (fixed period). The exam phrase "1000 calls per month" indicates quota; "10 calls per minute" indicates rate-limit.
Omitting `<base />` and thinking parent policies still apply. Candidates assume inheritance is automatic. In reality, if you omit <base />, the parent policy is completely overridden. The exam may present a scenario where a global policy is not taking effect, and the answer is to add <base />.
Placing `ip-filter` in outbound instead of inbound. IP filtering should be evaluated before processing the request. Outbound ip-filter would check the backend IP, not the client.
Forgetting `require-expiration-time` when validating JWT. The default is true; if the token lacks exp, validation fails. The exam may ask why valid tokens are rejected — answer: token has no expiration.
Choosing `limit-concurrency` for rate limiting. limit-concurrency limits simultaneous connections, not total calls over time.
Specific Numbers, Values, and Terms
renewal-period in seconds (e.g., 86400 for 1 day).
calls attribute in quota and rate-limit.
failed-validation-httpcode default is 401.
require-expiration-time default is true.
openid-config URL format: https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration.
Edge Cases and Exceptions
If no on-error policy is defined, APIM returns a 500 error with a generic message.
CORS policy must be applied to the API that handles the preflight (OPTIONS) request; otherwise, the browser blocks the request.
The ip-filter policy checks the X-Forwarded-For header if APIM is behind a load balancer. To trust the header, set forwarded-for="true".
validate-jwt can validate tokens from multiple issuers using <openid-config> or multiple <issuer> elements.
How to Eliminate Wrong Answers
If the requirement is about IP addresses, look for ip-filter.
If the requirement is about tokens, look for validate-jwt.
If the requirement is about call limits per time window, look for rate-limit; if absolute limit over a period, look for quota.
If the requirement is about simultaneous connections, look for limit-concurrency.
If the requirement is about cross-origin, look for cors.
Always check if the policy includes <base /> when inheritance is needed.
APIM policies are XML-based and execute in the order: inbound, backend, outbound, on-error.
Always include `<base />` to inherit parent policies unless overriding is intentional.
Use `validate-jwt` for Azure AD token validation; specify `openid-config` or explicit `issuer` and `audiences`.
Use `ip-filter` for client IP allow/deny; place it in inbound and set `forwarded-for="true"` if behind a proxy.
`rate-limit` controls rate per time window; `quota` controls total calls over a period.
CORS policy must handle OPTIONS preflight requests; specify allowed origins, methods, and headers.
The `on-error` section handles policy failures; if omitted, a generic 500 error is returned.
`require-expiration-time` defaults to true in `validate-jwt`; tokens without `exp` will be rejected.
`limit-concurrency` limits simultaneous connections, not total call count.
APIM policies are application-layer; combine with network security groups and firewalls for defense in depth.
These come up on the exam all the time. Here's how to tell them apart.
rate-limit
Limits call rate per time window (e.g., per second, per minute).
Resets at the end of each time window (sliding window).
Use for burst protection and fair usage.
Keyed by subscription key or caller IP.
Example: 10 calls per minute.
quota
Limits total calls over a fixed period (e.g., per day, per month).
Resets after the renewal period (fixed window).
Use for subscription-based limits.
Keyed by subscription key.
Example: 1000 calls per month.
Mistake
APIM policies are applied in the order they appear in the XML, but the `base` element is optional.
Correct
The `base` element is crucial for policy inheritance. If you omit `<base />`, the parent scope policies are not applied. The policy execution order is: inbound, backend, outbound, on-error; within each section, policies execute in the order defined.
Mistake
The `rate-limit` and `quota` policies are interchangeable.
Correct
They serve different purposes. `rate-limit` limits the rate of calls per time window (e.g., per minute) and resets at the end of each window. `quota` limits total calls over a fixed period (e.g., per day) and resets after the renewal period. Using the wrong one can lead to incorrect throttling behavior.
Mistake
JWT validation only requires the token; the `openid-config` is optional.
Correct
If you do not provide `openid-config`, you must explicitly specify the `issuer` and `audiences`. The `openid-config` URL is the simplest way to configure JWT validation as it fetches the public keys and issuer automatically. Without it, you risk misconfiguration.
Mistake
IP filtering in APIM can block traffic at the network level.
Correct
APIM IP filtering is application-layer; it only rejects requests after they reach the gateway. For network-level blocking, you need Azure Firewall, NSG, or Azure Front Door WAF. APIM policies are part of a defense-in-depth strategy, not a replacement for network security.
Mistake
CORS policy is only needed for the actual API responses.
Correct
CORS requires handling preflight OPTIONS requests. If the `cors` policy does not respond to OPTIONS with appropriate headers, the browser will block the actual request. The policy must be configured to allow the specific origin, methods, and headers.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
`rate-limit` limits the number of calls within a sliding time window (e.g., 10 calls per minute). It resets automatically at the end of each window. `quota` limits the total number of calls over a fixed period (e.g., 1000 calls per month) and resets after the specified `renewal-period`. Use `rate-limit` for burst protection and `quota` for subscription-based limits.
Add the `validate-jwt` policy in the inbound section. Specify the `header-name` (usually `Authorization`), `failed-validation-httpcode` (e.g., 401), and either an `openid-config` URL (e.g., `https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration`) or explicit `issuer` and `audiences`. Set `require-expiration-time="true"` to enforce token expiry.
Ensure the `ip-filter` policy is placed in the inbound section and that you have specified the correct IP addresses or ranges. If APIM is behind a load balancer, the client IP may be in the `X-Forwarded-For` header. Set `forwarded-for="true"` in the policy to use that header. Also check that no other policy is allowing the traffic.
The `<base />` element includes policies from the parent scope (global, product, or API). Without it, the parent policies are overridden and not applied. Always include `<base />` unless you explicitly want to replace all parent policies. It must be placed in each section (inbound, backend, outbound, on-error) to inherit that section's policies.
Add the `cors` policy to the API that will receive cross-origin requests. Specify `allowed-origins`, `allowed-methods`, and `allowed-headers`. The policy automatically handles preflight OPTIONS requests. Ensure the policy is applied to the API operation that handles the actual request, and that the response includes the `Access-Control-Allow-Origin` header.
Yes, use the `set-body` policy to modify the request or response body. You can also use `set-header` to modify headers. For complex transformations, use C# expressions within `@(...)` to manipulate JSON or XML. For example, `@(context.Request.Body.As<JObject>()["field"].ToString())`.
If no `on-error` section is defined, APIM returns a 500 Internal Server Error with a generic message. To provide custom error responses, define an `on-error` section with `set-status`, `set-header`, and `set-body` policies. You can also log errors to Application Insights using the `log-to-eventhub` policy.
You've just covered Azure API Management Security Policies — now see how well it sticks with free AZ-500 practice questions. Full explanations included, no account needed.
Done with this chapter?