Case Study: Restricting API Access by IP in Azure APIM
azureakhilsharmaapiapimsecurity

Case Study: Restricting API Access by IP in Azure APIM


Context

We recently ran into a challenge where we needed to expose an API publicly via Azure API Management (APIM), but restrict access to a specific API endpoint based on IP addresses.

The twist? The APIM gateway had to stay public because it served many other APIs—used by mobile apps, third-party clients, and internal dashboards. We couldn’t put a global IP restriction on the whole APIM. We needed to control access at the individual API level.

Here's how we solved it using APIM inbound policies—and some lessons learned along the way.


The Task

  • The APIM instance remains publicly accessible

  • Only a specific API (/secure-api) should be locked down to a defined set of IPs

  • All other APIs remain accessible to the public or other auth-secured clients


The Solution: Inbound Policy for IP Filtering

Azure APIM lets you define policy expressions at various scopes:

  • Product

  • API

  • Operation

We applied the restriction at the API level, which made it clean and easy to manage.

Step 1: List the Allowed IPs

We first defined a list of IPs we wanted to allow. This could be a single IP or a set of CIDR ranges:

<set-variable name="allowedIPs" value="'192.168.1.1,203.0.113.5,10.0.0.0/8'" />

Step 2: Check Client IP

Azure exposes the caller’s IP using context.Request.IpAddress. We added a check using a policy expression:

<check-header name="X-Forwarded-For" failed-check-httpcode="403" failed-check-error-message="Forbidden: IP not allowed">
    <value>@{
        var allowed = context.Variables.GetValueOrDefault<string>("allowedIPs").Split(',');
        var clientIp = context.Request.IpAddress;
        return allowed.Any(ip => clientIp.Equals(ip) || context.IpAddress.IsInRange(ip));
    }</value>
</check-header>

Final Policy Snippet

Here’s the complete policy applied at the API level:

<inbound>
    <base />
    <set-variable name="allowedIPs" value="'192.168.1.1,203.0.113.5'" />
    <check-header name="X-Forwarded-For" failed-check-httpcode="403" failed-check-error-message="Access Denied: Your IP is not allowed">
        <value>@{
            var allowed = context.Variables.GetValueOrDefault<string>("allowedIPs").Split(',');
            var clientIp = context.Request.IpAddress;
            return allowed.Contains(clientIp);
        }</value>
    </check-header>
</inbound>

Outcome

  • Our sensitive API is now fully IP-restricted

  • The rest of the APIM gateway remains public and functional

  • We’ve templatized this as a reusable policy for future secure endpoints