Skip to content

IP Whitelisting at the API Gateway

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you'll learn about IP Whitelisting. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

IP whitelisting restricts API access to specific IP addresses or ranges, providing an additional security layer by blocking all traffic from unauthorized networks.

What You'll Learn

By the end of this lesson, you will implement IP allow lists, CIDR range matching, geo-blocking, and dynamic IP management at the gateway.

Why It Matters

IP whitelisting prevents unauthorized networks from reaching your API even if authentication credentials are compromised. It is a critical defense-in-depth layer.

Real-World Use

An admin API only allows connections from the corporate VPN CIDR range (10.0.0.0/8) and the office static IPs, blocking all other traffic at the gateway.

IP Filtering Architecture

flowchart TD
    Request -->|Check IP| IPCheck{IP Allowed?}
    IPCheck -->|Yes| Auth[Authentication]
    IPCheck -->|No| Block[403 Forbidden]
    Auth -->|Pass| Backend[Backend Service]

CIDR Range Matching

# cidr_matcher.py
import ipaddress
from typing import List, Optional, Union

class CIDRWhitelist:
    def __init__(self):
        self.allowed_networks: List[ipaddress.IPv4Network] = []
        self.allowed_ips: List[ipaddress.IPv4Address] = []

    def add_network(self, cidr: str):
        self.allowed_networks.append(ipaddress.IPv4Network(cidr))

    def add_ip(self, ip: str):
        self.allowed_ips.append(ipaddress.IPv4Address(ip))

    def is_allowed(self, client_ip: str) -> bool:
        try:
            ip = ipaddress.IPv4Address(client_ip)
        except ValueError:
            return False

        if ip in self.allowed_ips:
            return True

        for network in self.allowed_networks:
            if ip in network:
                return True

        return False

    def allowed_ranges(self) -> List[str]:
        ranges = [str(n) for n in self.allowed_networks]
        ranges.extend([str(ip) for ip in self.allowed_ips])
        return ranges

whitelist = CIDRWhitelist()
whitelist.add_network("10.0.0.0/8")
whitelist.add_network("192.168.1.0/24")
whitelist.add_ip("203.0.113.42")

test_ips = ["10.1.2.3", "192.168.1.100", "203.0.113.42", "8.8.8.8", "invalid"]
for ip in test_ips:
    allowed = whitelist.is_allowed(ip)
    print(f"{ip:20s} -> {'ALLOWED' if allowed else 'BLOCKED'}")

Expected output:

10.1.2.3             -> ALLOWED
192.168.1.100        -> ALLOWED
203.0.113.42         -> ALLOWED
8.8.8.8              -> BLOCKED
invalid              -> BLOCKED

Geo-Blocking

# geo_blocking.py
from typing import Dict, List, Optional

class GeoBlocker:
    def __init__(self):
        self.blocked_countries: set = set()
        self.allowed_countries: set = set()

    def block_country(self, country_code: str):
        self.blocked_countries.add(country_code.upper())

    def allow_country(self, country_code: str):
        self.allowed_countries.add(country_code.upper())

    def is_allowed(self, country_code: str) -> bool:
        country_code = country_code.upper()

        if self.allowed_countries:
            return country_code in self.allowed_countries

        if self.blocked_countries:
            return country_code not in self.blocked_countries

        return True

    def check_request(self, request_ip: str, country_code: str) -> dict:
        allowed = self.is_allowed(country_code)
        return {
            "ip": request_ip,
            "country": country_code,
            "allowed": allowed,
            "reason": None if allowed else f"Traffic from {country_code} is blocked",
        }

geo = GeoBlocker()
geo.block_country("KP")
geo.block_country("IR")
geo.block_country("SY")

requests = [
    ("10.0.0.1", "US"),
    ("10.0.0.2", "KP"),
    ("10.0.0.3", "IR"),
    ("10.0.0.4", "DE"),
]

for ip, country in requests:
    result = geo.check_request(ip, country)
    print(f"{ip:15s} [{country}] -> {'ALLOWED' if result['allowed'] else 'BLOCKED (' + result['reason'] + ')'}")

Expected output:

10.0.0.1       [US] -> ALLOWED
10.0.0.2       [KP] -> BLOCKED (Traffic from KP is blocked)
10.0.0.3       [IR] -> BLOCKED (Traffic from IR is blocked)
10.0.0.4       [DE] -> ALLOWED

Dynamic IP Management

# dynamic_ip_mgmt.py
import time
from typing import Dict, List, Optional, Set

class DynamicIPManager:
    def __init__(self, cleanup_interval: int = 300):
        self.temp_whitelist: Dict[str, float] = {}
        self.perm_whitelist: Set[str] = set()
        self.cleanup_interval = cleanup_interval
        self.last_cleanup = time.time()

    def add_permanent(self, ip: str):
        self.perm_whitelist.add(ip)

    def add_temporary(self, ip: str, ttl_seconds: int = 3600):
        self.temp_whitelist[ip] = time.time() + ttl_seconds

    def remove(self, ip: str):
        self.perm_whitelist.discard(ip)
        self.temp_whitelist.pop(ip, None)

    def is_allowed(self, ip: str) -> bool:
        self._cleanup()

        if ip in self.perm_whitelist:
            return True

        if ip in self.temp_whitelist and time.time() < self.temp_whitelist[ip]:
            return True

        return False

    def _cleanup(self):
        now = time.time()
        if now - self.last_cleanup < self.cleanup_interval:
            return
        expired = [ip for ip, exp in self.temp_whitelist.items() if exp < now]
        for ip in expired:
            del self.temp_whitelist[ip]
        self.last_cleanup = now

manager = DynamicIPManager()
manager.add_permanent("192.168.1.100")
manager.add_temporary("10.0.0.50", ttl_seconds=5)

print("Immediately:")
print(f"  192.168.1.100: {manager.is_allowed('192.168.1.100')}")
print(f"  10.0.0.50:     {manager.is_allowed('10.0.0.50')}")
print(f"  1.2.3.4:       {manager.is_allowed('1.2.3.4')}")

Expected output:

Immediately:
  192.168.1.100: True
  10.0.0.50:     True
  1.2.3.4:       False

Common Mistakes

1. Not Handling X-Forwarded-For

The client IP may be in X-Forwarded-For header, not the connection IP when behind a load balancer. Parse the correct IP.

2. Overly Broad Ranges

Allowing 0.0.0.0/0 effectively disables IP filtering. Use the most specific CIDR ranges possible.

3. No Fallback for Misconfiguration

If the whitelist configuration is invalid, the gateway should default to deny, not allow.

4. Ignoring IPv6

Many clients use IPv6. Ensure your IP filtering handles both IPv4 and IPv6 addresses.

5. Not Monitoring Blocked Attempts

Blocked IP attempts can indicate scanning or attack patterns. Log and monitor blocked requests.

Practice Questions

1. What is a CIDR notation and how does it work?

CIDR (Classless Inter-Domain Routing) notation specifies an IP range using the base IP and prefix length (e.g., 10.0.0.0/8 means all IPs from 10.0.0.0 to 10.255.255.255).

2. Why combine IP whitelisting with authentication?

IP whitelisting blocks network-level attacks even if credentials are stolen. Authentication prevents unauthorized users within the allowed network.

3. How do you handle dynamic IPs for remote workers?

Use a temporary whitelist with TTLs that remote workers renew periodically, or require VPN access with static IPs.

4. What is geo-blocking and when should you use it?

Geo-blocking restricts traffic based on geographic region. Use it when your API targets specific regions to reduce attack surface.

Challenge

Design an IP access control system that combines permanent whitelist, temporary access with TTL, geo-blocking, and CIDR matching with proper X-Forwarded-For handling.

FAQ

Can IP whitelisting be bypassed?

IP spoofing is possible but difficult because responses go to the spoofed IP. Combined with authentication, it is effective.

Should IP whitelisting be the only security layer?

No. Use defense in depth: IP whitelisting + authentication + rate limiting + WAF.

How do cloud providers affect IP whitelisting?

Cloud functions and serverless have dynamic IPs. Use service-level security instead of IP-based for these.

Is geo-blocking reliable?

Geo-IP databases are accurate at the country level but less precise for cities. Use it as a rough filter.

How do you test IP filtering?

Set up test IPs in your whitelist and verify that allowed and blocked IPs behave correctly. Use integration tests.

Mini Project: IP Access Controller

# ip_controller.py
import ipaddress
import time
from typing import Dict, List, Optional, Set

class IPAccessController:
    def __init__(self):
        self.whitelist = []
        self.blacklist = []
        self.temp_access = {}

    def add_whitelist(self, cidr: str):
        self.whitelist.append(ipaddress.IPv4Network(cidr))

    def add_blacklist(self, cidr: str):
        self.blacklist.append(ipaddress.IPv4Network(cidr))

    def grant_temp(self, ip: str, ttl: int = 300):
        self.temp_access[ip] = time.time() + ttl

    def check(self, client_ip: str) -> dict:
        try:
            ip = ipaddress.IPv4Address(client_ip)
        except ValueError:
            return {"allowed": False, "reason": "Invalid IP"}

        for net in self.blacklist:
            if ip in net:
                return {"allowed": False, "reason": f"IP in blacklist {net}"}

        if client_ip in self.temp_access and time.time() < self.temp_access[client_ip]:
            return {"allowed": True, "reason": "Temporary grant"}

        if not self.whitelist:
            return {"allowed": True, "reason": "No restrictions"}

        for net in self.whitelist:
            if ip in net:
                return {"allowed": True, "reason": f"IP in whitelist {net}"}

        return {"allowed": False, "reason": "IP not in whitelist"}

ctrl = IPAccessController()
ctrl.add_whitelist("10.0.0.0/8")
ctrl.add_blacklist("10.0.0.0/24")
ctrl.grant_temp("10.0.0.50", 60)

tests = ["10.0.0.50", "10.0.0.1", "10.0.1.1", "192.168.1.1"]
for ip in tests:
    result = ctrl.check(ip)
    print(f"{ip:20s} -> {'ALLOWED' if result['allowed'] else 'BLOCKED'}: {result['reason']}")

Expected output:

10.0.0.50            -> ALLOWED: Temporary grant
10.0.0.1             -> BLOCKED: IP in blacklist 10.0.0.0/24
10.0.1.1             -> ALLOWED: IP in whitelist 10.0.0.0/8
192.168.1.1          -> BLOCKED: IP not in whitelist

What's Next

You understand IP whitelisting. Next, learn about circuit breaker pattern, then explore gateway caching.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro