
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