Saturday, December 27, 2025

Securing Azure API Management with Policies

Azure API Management sits between API consumers and backend services, and policies are the mechanism through which you enforce security controls, shape traffic, and transform messages. A well-composed policy pipeline in APIM produces consistent security posture across all APIs without requiring changes to backend code.

This post covers the four policies that should be in place on any production APIM instance: rate limiting, JWT validation, IP filtering, and response header cleanup.

1. Policy Scopes and Execution Order

Policies in Azure API Management can be applied at four levels:

ScopeApplied to
GlobalEvery API in the instance
ProductAll APIs assigned to a product
APIAll operations within a specific API
OperationA single HTTP operation (e.g., POST /orders)

Policies execute from the outermost scope inward. A global inbound policy runs before an API-level inbound policy, which runs before an operation-level policy. The <base /> element controls where the parent scope's policies execute relative to the current scope. Omitting <base /> prevents parent policies from running — which is occasionally intentional but more often an accidental override that disables security controls silently.

To open the policy editor:

  1. Navigate to API Management > APIs
  2. Select the target API or individual operation
  3. Open the Design tab
  4. Select the pencil icon next to Inbound processingOutbound processing, or Backend

2. Rate Limiting with rate-limit-by-key

The rate-limit-by-key policy restricts how many calls a single consumer can make within a rolling time window. The counter key is a policy expression — typically the subscription key, a JWT claim, or the caller's IP address.

Following is a policy limiting each subscription to 100 calls per 60 seconds:

<inbound>
  <base />
  <rate-limit-by-key
    calls="100"
    renewal-period="60"
    counter-key="@(context.Subscription?.Key ?? string.Empty)"
    increment-condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300)"
  />
</inbound>

Setting increment-condition to only count successful responses avoids penalising consumers for backend errors outside their control. When the limit is exceeded, APIM returns 429 Too Many Requests. I recommend pairing this with a Retry-After header in the outbound section so clients know when the window resets.

Apply this policy at the Product scope so it covers all APIs within that product, then override at the operation level only for endpoints that legitimately require different limits.

3. JWT Validation

The validate-jwt policy validates a JSON Web Token before the request reaches the backend — checking the signature, expiry, and required claims. For APIs secured with Microsoft Entra ID, this eliminates an entire class of authentication bypass risks that arise when each backend independently validates tokens.

Following is a policy that validates an Entra ID-issued token:

<inbound>
  <base />
  <validate-jwt
    header-name="Authorization"
    failed-validation-httpcode="401"
    failed-validation-error-message="Unauthorised. A valid bearer token is required.">
    <openid-config url="https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration" />
    <required-claims>
      <claim name="aud">
        <value>api://<app-id></value>
      </claim>
    </required-claims>
  </validate-jwt>
</inbound>

The <openid-config> element fetches and caches the provider's signing keys automatically. The <required-claims> block ensures the token was issued specifically for this API's audience, preventing a valid token for one API from being replayed against another. Requests without a valid bearer token receive the configured 401 response and never reach the backend.

4. IP Filtering

The ip-filter policy evaluates the caller's IP address against an allowlist or blocklist. It is most useful for internal or partner-facing APIs that should only accept traffic from known networks — office IP ranges, VPN exit nodes, or peered virtual networks.

Following is an allowlist policy permitting a CIDR range and a specific address:

<inbound>
  <base />
  <ip-filter action="allow">
    <address-range from="10.0.0.0" to="10.0.0.255" />
    <address>203.0.113.42</address>
  </ip-filter>
</inbound>

When API Management is deployed behind an Azure Application Gateway or Azure Front Door, the caller IP seen by APIM is the gateway's address, not the client's original IP. In this scenario, extract the original client IP from the X-Forwarded-For header using a set-variable policy before the ip-filter evaluation.

5. Response Header Cleanup

Backend services frequently return headers that expose internal implementation details — server software versions, framework identifiers, or internal hostnames. These are useful during development but should not reach API consumers in production.

Following is an outbound policy that removes common information-disclosure headers and adds a security response header:

<outbound>
  <base />
  <set-header name="X-Powered-By" exists-action="delete" />
  <set-header name="X-AspNet-Version" exists-action="delete" />
  <set-header name="Server" exists-action="delete" />
  <set-header name="X-Content-Type-Options" exists-action="override">
    <value>nosniff</value>
  </set-header>
  <set-header name="X-Frame-Options" exists-action="override">
    <value>DENY</value>
  </set-header>
</outbound>

Apply this at the Global scope so it covers every API without repeating it per-API. Backend teams can then focus on returning correct data — APIM handles response hygiene uniformly.

Summary

APIM policies provide a central enforcement point for security controls that would otherwise be replicated, inconsistently, across every backend service. Rate limiting at the product scope, JWT validation at the API scope, IP filtering for restricted APIs, and outbound header cleanup at the global scope form a baseline security posture that is both effective and maintainable. Start with these four policies and tighten them at the operation level only where specific endpoints require different behaviour.

Thursday, December 25, 2025

Planning Your Azure Budget for the Year Ahead: A Practical Framework

The end of the calendar year is the right time to review actual Azure spend, assess what changed during the year, and set a realistic budget for the year ahead. A well-structured budget is not just a financial control; it is a governance tool that keeps engineering and finance aligned throughout the year.

This post outlines a practical framework for reviewing the current year's spend and setting up Azure budgets for the next 12 months.

1. Reviewing the Current Year's Spend

Before setting next year's budget, it is important to understand this year's patterns, particularly which resource groups grew significantly, which were decommissioned, and whether any anomalies inflated the total.

  1. Navigate to Cost Management + Billing > Cost Analysis
  2. Set the Time range to This year (or the last 12 months if the current year started recently)
  3. Set Granularity to Monthly and Group by to Resource group

This view shows the month-by-month cost trend per resource group. Look for:

  • Resource groups with consistent growth: these need a higher budget allocation next year
  • Months with spikes: investigate whether these were one-time events (migrations, incidents) or recurring patterns
  • Resource groups with zero activity in recent months: candidates for decommission

2. Estimating Next Year's Budget

A practical estimation approach is to take the last three months of spend (Q4 of the current year), calculate the average monthly cost, and apply a growth factor based on planned workload changes.

Following is a simple framework:

InputExample
Average monthly spend (last 3 months)$4,200
Planned new workloads+15%
Expected optimisation savings-10%
Estimated monthly budget$4,410
Annual budget$52,920

Add a buffer of 5–10% to the annual budget to account for unplanned usage. Setting the budget too tightly leads to constant alert noise; setting it too loosely removes the governance benefit.

3. Creating Annual and Monthly Budgets

Azure Cost Management supports budgets at multiple time grains. For annual planning, I recommend creating both an annual budget at subscription or management group scope and monthly budgets at individual resource group scope.

To create a budget:

  1. Navigate to Cost Management + Billing > Cost Management > Budgets > + Add
  2. Set the Reset period. Select Annually for the top-level budget and Monthly for resource group budgets
  3. Set the Budget amount based on the estimate from Step 2
  4. Configure alert thresholds at 50%80%, and 100%
  5. Add email recipients for each threshold. Include both the engineering lead and a finance contact

4. Using the Forecast to Validate the Budget

Azure Cost Management includes a spend forecast based on historical usage patterns. This is a useful sanity check before finalising budget amounts.

Navigate to Cost Management > Cost Analysis and set the view to Accumulated costs. The forecast line (shown in a lighter colour beyond the current date) projects spending to end of period based on current trajectory.

If the forecast significantly exceeds the proposed budget, either adjust the budget upward or identify specific optimisation actions that will reduce spend before the new year begins.

5. Scheduling a Quarterly Budget Review

A budget set in January rarely reflects reality by June. Build in a quarterly review. At each review, compare actual spend against budget, assess whether planned workloads have materialised as expected, and adjust budgets or resource allocations accordingly.

Summary

Effective Azure budget planning starts with an honest review of the current year's data, followed by a realistic estimate that accounts for planned growth and known optimisations. Configuring budgets with graduated alert thresholds (not just a single 100% alert) ensures that teams have time to respond before limits are reached, avoiding surprises at year-end billing reconciliation.