Skip to content

Introduction to APIM Policy Scopes

This document explains the different policy scopes available in Azure API Management (APIM) and provides examples for each scope. Understanding policy scopes is essential for properly configuring how your APIs handle requests, validate tokens, transform data, and enforce security measures.

For comprehensive documentation on APIM policies, refer to the Microsoft documentation.

All policies below are managed in the infrastructure repository as part of the API configuration.

Policy Scopes

API Management enables you to define policies at the following scopes, presented here from broadest to narrowest:

  • Global - Applies to all APIs
  • Workspace - Applies to all APIs associated with a selected workspace (not used in our setup)
  • Product - Applies to all APIs associated with a selected product
  • API - Applies to all operations in an API
  • Operation - Applies to a single operation in an API

When configuring a policy, you must first select the scope at which the policy applies. Policies defined at narrower scopes inherit from broader scopes using the <base /> element, which includes the parent policy configuration.

WARNING

As a best practice, include a <base /> element at the beginning of each policy section to inherit policies from the parent scope

Policy Structure

Each policy definition consists of four main sections:

  • inbound - Throttle, authorize, validate, cache, or transform the requests
  • backend - Control if and how the requests are forwarded to services
  • outbound - Customize the responses
  • on-error - Handle exceptions and customize error responses

Our Policies config by Scope

Global Scope

Policies at the global scope apply to all APIs in APIM. This is typically used for organization-wide settings like CORS for the developer portal.

INFO

The terminate-unmatched-request="false" attribute is important for global CORS policies and product-level CORS policies to coexist without conflict with API policies.

xml
<policies>
    <inbound>
        <cors allow-credentials="true" terminate-unmatched-request="false">
            <allowed-origins>
                <origin>https://equation-acceptance.developer.azure-api.net</origin>
            </allowed-origins>
            <allowed-methods preflight-result-max-age="300">
                <method>*</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
            <expose-headers>
                <header>*</header>
            </expose-headers>
        </cors>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound />
    <on-error>
        <choose>
            <when condition="@(context.Response != null && context.Response.StatusCode == 404)">
                <set-status code="404" reason="Routes Not Found in APIM" />
                <set-header name="Content-Type" exists-action="override">
                    <value>application/json</value>
                </set-header>
                <set-body>@{
                        return new JObject(
                            new JProperty("statusCode", 404),
                            new JProperty("message", "The requested API endpoint could not be found in API Management. Please verify the URL or route.")
                        ).ToString();
                    }</set-body>
            </when>
        </choose>
    </on-error>
</policies>

This file's content is sync with infrastructure repository at /global_policy/global-policy.xml.tpl

Product Scope

Policies at the product scope apply to all APIs associated with a specific product. This is useful for grouping APIs that share common requirements like CORS origins or rate limits.

xml
<policies>
    <inbound>
        <base />
        <cors allow-credentials="true" terminate-unmatched-request="false">
            <allowed-origins>
                <origin>https://localhost:3000</origin>
                <origin>http://localhost:9999</origin>
                <origin>https://equation-excel.haskoning.app</origin>
                <origin>https://equation-excel-acceptance.haskoning.app</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
                <method>PUT</method>
                <method>DELETE</method>
                <method>HEAD</method>
                <method>OPTIONS</method>
                <method>PATCH</method>
            </allowed-methods>
        </cors>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

INFO

Note that the <base /> element in each section, which inherits policies from the parent scope (in this case, the global scope). Please check on each policy to ensure the behaviour of them are inherited or overridden policies are as expected.

See the files in the infrastructure repository under /product_policies/ for each product.

API Scope

Policies at the API scope apply to all operations within a specific API. This is the most common scope for defining authentication, backend routing, and API-specific transformations. By default, all APIs added to Equation have JWT validation and forwarding headers configured at this scope.

Example: JWT validation and custom headers for an API

xml
<policies>
    <inbound>
        <base />
        <allowed-origins>
                <origin>https://your.frontend.etc</origin>
        </allowed-origins>
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
            <issuers>
                <issuer>https://sts.windows.net/{tenant-id}/</issuer>
                <issuer>https://login.microsoftonline.com/{tenant-id}/v2.0</issuer>
            </issuers>
        </validate-jwt>
        <set-backend-service backend-id="shield-runner-backend" />
        <set-header name="X-Forwarded-Host" exists-action="override">
            <value>@(context.Request.OriginalUrl.Host)</value>
        </set-header>
        <set-header name="X-Forwarded-Proto" exists-action="override">
            <value>@(context.Request.OriginalUrl.Scheme)</value>
        </set-header>
        <set-header name="X-Forwarded-Prefix" exists-action="override">
            <value>/shield-runner</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

This example demonstrates:

  • Inheriting parent policies with <base />
  • Validating JWT tokens for authentication
  • Setting a custom backend service
  • Adding forwarding headers for proper request routing

In the infrastructure repository, you can find the API-level policies under /api_policies/. the default policy file is named default_policy.xml.tpl and is used as a template to generate a similar policy above for each endpoint.

Operation Scope

Policies at the operation scope apply to a single endpoint within an API. This is useful for endpoint-specific requirements like different rate limits or response transformations.

Operation-level policies follow the same structure as API-level policies but provide the finest level of control. Use this scope sparingly to avoid overly complex configurations.

Best Practices

  • Start Broad, Refine Narrow: Define common policies at broader scopes (global or product) and override or extend them at narrower scopes (API or operation) as needed.
  • Use <base /> Consistently: Always include <base /> in child policies to inherit parent configurations unless you explicitly want to replace them entirely.
  • Document Policy Intent: Add comments in your policy XML to explain the purpose of complex configurations.
  • Test Thoroughly: Changes to policies can affect multiple APIs or operations, so test carefully in non-production environments first.