API & Microservices Architecture
Microservices architectures introduce new security challenges: more network communication, more attack surface, and complex trust relationships. Securing APIs becomes critical.
API & Microservices Request Flow
API Gateway Security
The API Gateway is your first line of defense. It centralizes security controls before requests reach backend services.
The gateway is attractive because it is the one place an organization can enforce
authentication, authorization, and traffic policy without asking every service team to
reimplement them. It is also a single point of failure: a misconfigured route, an over-broad
OAuth scope, or a JWT check that trusts the alg header can expose every backend at
once. Treat gateway configuration as production code — reviewed, versioned, and tested — and use
the
OWASP API Security Top 10 (2023)
to decide what each plugin must enforce rather than enabling safeguards ad hoc.
Authentication
- • OAuth 2.0 / OpenID Connect
- • API key validation
- • JWT verification
- • mTLS for B2B APIs
Traffic Management
- • Rate limiting per client
- • Request throttling
- • Quota enforcement
- • DDoS protection
Request Validation
- • Schema validation (OpenAPI)
- • Input sanitization
- • Request size limits
- • Content-Type enforcement
Observability
- • Request/response logging
- • Metrics and tracing
- • Anomaly detection
- • Audit trails
Service-to-Service Authentication
Once traffic is inside the perimeter, every call between services still needs a verifiable identity — network location is no longer an authorization signal. The pattern is a workload identity (an mTLS SPIFFE identity, a Kubernetes service account, or a cloud managed identity) exchanged for a short-lived credential the receiving service validates. The common failure is long-lived static API keys shared between services: they cannot be scoped to a single caller, do not rotate, and survive in config files and CI variables long after the service they belong to is decommissioned. Prefer credentials that expire in minutes over secrets that live for years, and verify tokens at the receiving service rather than trusting anything the caller asserts.
Mutual TLS (mTLS)
Both client and server present certificates. Provides authentication and encryption.
- • Typically managed by service mesh
- • Automatic certificate rotation
- • No application code changes needed
Service Account Tokens
Short-lived tokens issued to services (Kubernetes service accounts, Azure Managed Identities).
- • Workload identity in cloud environments
- • No static credentials to manage
- • Automatic token refresh
JWT Service Tokens
Services authenticate with signed JWTs containing claims about identity and permissions.
- • Signed by trusted issuer
- • Contains scope/permissions claims
- • Verify signature and claims at each service
Multi-Tenancy & Tenancy Boundaries
A multi-tenant service runs one deployment for many customers, so every query, cache key, queue message, and rate-limit bucket carries an implicit "which tenant?" question. Tenancy boundaries are the enforcement of that question. Vendor guidance (AWS SaaS Lens, Azure Architecture Center) describes three isolation models, and most real systems combine them:
Silo
Per-tenant infrastructure — separate databases, namespaces, or accounts. Strongest isolation and the easiest to evidence in an audit; costs the most to operate and can hide cross-tenant trends.
Pool
Tenants share infrastructure with a tenant_id discriminator on every row and record. Lowest cost and highest density; isolation now depends entirely on correct, mandatory filtering.
Bridge
Pool by default, with the option to place a large or regulated tenant on dedicated resources. Balances cost and isolation, at the price of operating both models.
Authorization must be tenant-aware. A token should carry the tenant claim
(tenant_id, org_id, or an issuer that maps to one), and every access
check should be a (subject, tenant, resource) decision rather than a (subject, resource) one.
Never trust a tenant identifier supplied in the request body or URL — derive it from the
authenticated token and reject requests whose path or body names a different tenant. A user who
is an administrator in tenant A must be a non-member in tenant B.
Data partitioning follows from the model. In a pool, enforce row-level security in the database
(PostgreSQL RLS, or an equivalent policy engine) so a missing tenant_id predicate in
application code is caught at the storage layer instead of silently returning another tenant's
rows. Set the tenant context per connection or transaction — not per query — and make the default
deny when no context is set. Cache keys, object-storage prefixes, search indexes, and background
jobs need the same discriminator: a shared cache key or a queued message without a tenant is a
boundary that does not exist.
Rate limits and quotas are per tenant, not per deployment. Cap requests, storage, and concurrent
jobs against the tenant claim so one noisy tenant cannot exhaust shared capacity, and apply the
limit after authentication so an unauthenticated caller cannot spend a tenant's bucket. The
classic failure mode is cross-tenant data exposure: an object reference such as
/invoices/1042 is authorized against the caller's identity but not the invoice's
tenant, so changing the id returns another customer's data — OWASP API Security API1 (Broken
Object Level Authorization). Test for it explicitly: for every resource endpoint, authenticate as
tenant A, request tenant B's identifiers, and confirm each returns 404 or 403.
OWASP API Security Top 10 (2023)
| ID | Risk | Mitigation |
|---|---|---|
| API1 | Broken Object Level Authorization | Check ownership at resource level, not just endpoint |
| API2 | Broken Authentication | OAuth 2.0 + PKCE, rotate tokens, rate-limit auth endpoints |
| API3 | Broken Object Property Level Authorization | Return only allowed fields per role, never mass-assign |
| API4 | Unrestricted Resource Consumption | Rate limit, pagination, max payload size, timeout |
| API5 | Broken Function Level Authorization | Consistent authz middleware, deny by default |
| API6 | Unrestricted Access to Sensitive Business Flows | CAPTCHA, device fingerprinting, anti-automation |
| API7 | Server Side Request Forgery | URL allowlists, block internal ranges, validate schemes |
| API8 | Security Misconfiguration | Hardened defaults, automated config scanning |
| API9 | Improper Inventory Management | API catalog, deprecation policy, version sunset dates |
| API10 | Unsafe Consumption of APIs | Validate 3rd-party responses, timeout, circuit break |
Rate Limiting Strategies
A rate limit protects shared capacity — it is not only an abuse control. The algorithm you pick decides whether a legitimate client can burst, how much state the limiter holds, and whether the limit holds across multiple gateway nodes. All four common algorithms are expressible with the practical that follows.
Fixed Window
100 requests per minute. Counter resets at minute boundary.
Simple but can allow burst at window edges
Sliding Window
Smooths rate over rolling time period.
Better distribution, more memory needed
Token Bucket
Tokens refill at fixed rate. Each request consumes a token.
Allows controlled bursting
Leaky Bucket
Requests queue and process at constant rate.
Smoothest output, adds latency
Practical: Redis Sliding Window Rate Limiter
"""Sliding window rate limiter using Redis sorted sets."""
import time
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
def is_rate_limited(client_id: str, max_requests: int = 100, window_sec: int = 60) -> bool:
"""Returns True if the client has exceeded their rate limit."""
key = f"ratelimit:{client_id}"
now = time.time()
window_start = now - window_sec
pipe = r.pipeline()
# Remove expired entries
pipe.zremrangebyscore(key, 0, window_start)
# Add current request
pipe.zadd(key, {f"{now}": now})
# Count requests in window
pipe.zcard(key)
# Set TTL so keys auto-expire
pipe.expire(key, window_sec)
results = pipe.execute()
request_count = results[2]
return request_count > max_requests
# Usage in Flask middleware:
@app.before_request
def check_rate_limit():
# Key on the authenticated principal/tenant established by auth middleware.
# Never key on a client-supplied header like X-API-Key: a caller can rotate
# fake keys to get a fresh bucket and bypass the limit entirely.
client_id = g.get("principal_id") or g.get("tenant_id")
if not client_id:
# Fall back to the client IP only when running behind a trusted reverse
# proxy that overwrites X-Forwarded-For (otherwise the IP is spoofable too).
client_id = request.remote_addr
if is_rate_limited(client_id, max_requests=100, window_sec=60):
return jsonify({"error": "rate_limit_exceeded"}), 429Practical: Kong API Gateway Security Config
# Kong declarative config — security plugins (Kong Gateway OSS)
_format_version: "3.0"
services:
- name: backend-api
url: http://api-server:8080
routes:
- name: api-route
paths: ["/api/v1"]
strip_path: false
plugins:
# JWT authentication. Kong's JWT plugin does NOT resolve JWKS by "kid":
# it matches a Consumer by key_claim_name and verifies with that consumer's
# stored secret/key. Key on a claim that identifies the issuer/consumer
# (e.g. "iss"), not "kid". For JWKS/kid-aware verification, use a plugin
# that fetches the JWKS (or validate the token upstream).
- name: jwt
config:
claims_to_verify: ["exp"]
key_claim_name: "iss"
secret_is_base64: false
# Rate limiting - the OSS rate-limiting plugin runs a fixed window per node.
# Sliding-window limiting shared across nodes needs rate-limiting-advanced,
# which ships with Kong Enterprise / Konnect (plus a Redis datastore).
- name: rate-limiting
config:
minute: 100
policy: local
# Request size limiting (prevent DoS)
- name: request-size-limiting
config:
allowed_payload_size: 1 # 1 MB max
# CORS
- name: cors
config:
origins: ["https://app.example.com"]
methods: ["GET", "POST", "PUT", "DELETE"]
headers: ["Authorization", "Content-Type"]
max_age: 3600
- name: admin-api
url: http://admin-server:8081
routes:
- name: admin-route
paths: ["/admin"]
plugins:
# IP restriction for the admin route
- name: ip-restriction
config:
allow: ["10.0.0.0/8"]
deny: []API Security Tools
| Category | Tool | Purpose |
|---|---|---|
| Gateway | Kong, Envoy, APISIX | Centralized auth, rate limiting, routing |
| Testing | 42Crunch, OWASP ZAP, Burp | API security scanning, fuzzing |
| Governance | Spectral, Optic, Redocly | OpenAPI linting, breaking change detection |
| Discovery | Treblle, Akto, Salt Security | Shadow API detection, inventory |
Circuit Breaker Pattern
Circuit breakers prevent cascade failures by stopping calls to failing services.
A breaker is a state machine wrapped around a remote call, and it needs a failure threshold, a timeout, and a recovery probe to be useful. Without them it either never trips — and the cascade continues — or it trips on a single transient error and stays open. The subtle failure is choosing the wrong granularity: one breaker shared by every operation on a dependency will open for all of them when a single endpoint degrades, while a breaker per call site multiplies the state you must reason about. Pair breakers with timeouts, bounded retries with jitter, and a fallback that degrades gracefully instead of failing the whole request.
CLOSED
Normal operation. Requests pass through. Failures counted.
OPEN
Failure threshold exceeded. Requests fail fast. No calls to service.
HALF-OPEN
Testing recovery. Limited requests allowed. Success = close, failure = open.
API Security Checklist
- ☐ All APIs require authentication
- ☐ Authorization checked at resource level
- ☐ Input validated against schema
- ☐ Rate limiting configured per client tier
- ☐ Response doesn't leak internal details
- ☐ HTTPS only, TLS 1.2+
- ☐ Security headers set (CORS, CSP for browser APIs)
- ☐ API versioning strategy defined
- ☐ Deprecation policy for old versions
- ☐ Documentation doesn't expose vulnerabilities
Framework Alignment
NIST CSF 2.0: PR.AA (Identity & Access Control), PR.DS (Data Security)
ISO 27002:2022: A.8.26 (Application Security Requirements), A.8.28 (Secure Coding)
CIS Controls v8.1: 16 (Application Software Security), 13 (Network Monitoring & Defense)
Related: Security Frameworks →
API & Microservices Labs
Secure the gateway, isolate tenants, and stop cascading failures in a distributed system.