API Gateway Security Patterns: Rate Limiting, mTLS, and Zero Trust for Microservices

A practical guide to securing REST APIs with JWTs, scopes, and rate limiting, including working middleware code.

Why Gateway-Level Security Matters in a Microservices World

Once you have more than a handful of services, enforcing auth and rate limiting independently in each one leads to inconsistency and duplicated logic. An API gateway centralizes these concerns so individual services can trust that traffic reaching them has already been vetted.

Centralized JWT Validation at the Gateway

# Kong Gateway plugin config
plugins:
  - name: jwt
    config:
      key_claim_name: kid
      claims_to_verify:
        - exp
  - name: rate-limiting
    config:
      minute: 100
      policy: redis
      redis_host: redis.internal

Downstream services receive already-validated requests with identity passed through a trusted header, rather than each service independently verifying tokens.

Service-to-Service Auth with mTLS

External-facing auth (JWTs, API keys) secures client-to-gateway traffic. Internal service-to-service traffic should use mutual TLS so services can verify each other’s identity, not just encrypt the connection:

# Istio PeerAuthentication enforcing strict mTLS
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT

Zero Trust: Never Trust the Network Perimeter Alone

Traditional architecture trusted anything inside the VPC. Zero trust assumes a compromised service can exist anywhere, and enforces identity verification on every request, internal or external:

# Istio AuthorizationPolicy: only the checkout service may call payments
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: payments-access
  namespace: production
spec:
  selector:
    matchLabels:
      app: payments
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/production/sa/checkout-service"]

Rate Limiting at Multiple Layers

  • Gateway level — protects against abuse from external clients
  • Per-service level — protects individual services from internal cascading failures
  • Per-tenant level — prevents one customer’s traffic spike from degrading service for others
const tenantLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: (req) => req.tenant.plan === 'enterprise' ? 1000 : 100,
  keyGenerator: (req) => req.tenant.id,
});

Circuit Breaking to Contain Failures

# Istio DestinationRule with circuit breaking
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: payments-circuit-breaker
spec:
  host: payments
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s

Conclusion

Securing a microservices architecture is a layered problem: gateway-level auth and rate limiting for external traffic, mTLS and explicit authorization policies for internal service-to-service calls, and circuit breaking to contain the blast radius when something does go wrong. No single layer is sufficient on its own.