An API becomes vulnerable not because of a single missing header, but because its security contract is weak from the beginning.
The chapter assembles API protection from several layers: identity and authorization checks, rate limiting, schema validation, anti-replay, abuse prevention, and a secure lifecycle for the interface itself.
In architecture discussions, it is a good frame for analyzing the public edge, internal APIs, trust zones, and why interface security begins before the first endpoint is even added to OpenAPI.
Practical value of this chapter
Design in practice
Use guidance on API security patterns, security contracts, and abuse mitigation to define architectural security requirements before implementation starts.
Decision quality
Validate solutions through threat model, security invariants, and production control operability, not compliance checklists alone.
Interview articulation
Frame answers as threat -> control -> residual risk, linking business scenario to concrete protection mechanisms.
Trade-off framing
Make trade-offs explicit for API security patterns, security contracts, and abuse mitigation: UX friction, latency overhead, operational cost, and compliance constraints.
Context
OWASP Top 10 in the context of System Design
API security turns generic OWASP risks into concrete defenses for your system's specific API surface.
An API is the most exposed surface a system has: its address is known, its docs are public, and behind it sit other people's data and money. API Security Patterns are the set of choices that keep that surface working under load, in multi-tenant settings, and against an active adversary. There is no single "security" to switch on — it is assembled from authentication protocols, access policy, abuse protection, and observability, and every layer costs latency and money.
Core API security patterns
Strong AuthN + short-lived tokens
OIDC/OAuth2, mTLS for service-to-service, token rotation, and clear revocation strategy.
Fine-grained AuthZ
Authentication says who; authorization decides what they may touch: RBAC/ABAC, tenant-scoped permissions, policy engines, and deny-by-default — in the gateway and in every service.
Input/Schema validation
Everything from outside is untrusted input: strict JSON schema/OpenAPI checks, canonicalization, and rejection of unknown fields so a stray attribute never reaches the model.
Rate limiting + abuse controls
Per user/app/tenant quotas keep cost and availability in check; add burst control, bot detection, and adaptive throttling.
Anti-replay and request signing
Nonce + timestamp + HMAC/signature, clock-skew policy, and request expiration windows.
Zero-trust network posture
The internal network is not a trust boundary: no internal API is treated as friendly by default — hence mutual authentication and network segmentation.
Typical attack scenarios
BOLA/IDOR on object-level APIs
Risk: A user reaches another tenant's objects with valid authentication but weak authorization.
Mitigation: Object-level policy checks, tenant isolation, deny-by-default, and ownership validation.
Token replay
Risk: A captured token is replayed and used for privileged operations.
Mitigation: Short-lived tokens, nonce/timestamp checks, sender-constrained tokens, and anomaly detection.
Mass assignment and schema abuse
Risk: A request carries extra fields and, without an explicit filter, they get written into the model — the attacker changes an attribute that should be off-limits.
Mitigation: Strict allow-list schemas, reject unknown fields, and server-side checks of which fields belong to this client at all.
API scraping and business abuse
Risk: The attack never breaks authentication — it rides legitimate endpoints for large-scale scraping or fraud, and ordinary access checks never notice.
Mitigation: Adaptive rate limits, behavior signals, per-tenant quotas, and layered anti-bot controls.
Spec
OAuth 2.0 DPoP (RFC 9449)
The application-layer proof-of-possession spec: proof JWT structure, jkt binding, and nonces.
Sender-constrained tokens: DPoP and mTLS-bound
A Bearer token works on the principle of "whoever carries it owns it": an intercepted token is fully functional. A sender-constrained token is cryptographically bound to the client's key — stealing the token without stealing the key is useless. The two standard binding mechanisms are dissected below.
DPoP — application-layer proof of possession
RFC 9449How it works
- The client generates a key pair: the private key stays with the client (in browsers, a non-extractable WebCrypto key), the public key is shared with the server.
- For every request the client creates a DPoP proof — a compact JWT with header
typ: "dpop+jwt"and the public key injwk, signed with the private key. - Inside the proof:
jti(a unique identifier for replay detection),htm(HTTP method),htu(target URI without query and fragment),iat; when calling an API, alsoath— the SHA-256 hash of the access token. - The authorization server embeds the confirmation
cnf.jktinto the token — the base64url JWK SHA-256 thumbprint of the public key. The token is sent asAuthorization: DPoP <token>, not Bearer. - The resource server validates the proof signature, checks that
htm/htumatch the actual request, thatiat/jtiare fresh, and thatjktequals the key from the proof. - Against pre-generated proofs the server issues a value in the
DPoP-Nonceheader and responds with theuse_dpop_nonceerror; the client retries with the nonce included in the proof.
Protects against: A stolen access token is useless without the private key; the htm/htu binding prevents replaying the request against another endpoint; the server nonce breaks proofs pre-generated during an XSS theft.
Cost: A JWT signature on every request (CPU and latency), server-side jti storage for replay detection, an extra retry loop on use_dpop_nonce, and noticeably more complex client SDKs.
mTLS-bound tokens — channel-level binding
RFC 8705How it works
- The client establishes an mTLS connection to the authorization server and presents an X.509 certificate: the PKI method
tls_client_auth(a certificate from a trusted CA) orself_signed_tls_client_auth(the key registered viajwks). - The server embeds the confirmation
cnfwith thex5t#S256member into the issued token — the base64url SHA-256 hash of the DER encoding of the client certificate. - Every API call also runs over mTLS: the resource server takes the certificate from the TLS layer and compares its hash with the
x5t#S256value in the token. - If the hashes do not match, the request is rejected with 401 and the
invalid_tokenerror code: the token was presented by a client other than the one it was issued to.
Protects against: The same proof of possession as DPoP, but with no per-request signing: the channel itself proves key ownership. As a bonus, mTLS also covers client authentication at the token endpoint.
Cost: mTLS up to the edge is painful with CDNs and load-balancer TLS termination (the certificate must be forwarded to the application), plus PKI and certificate rotation; practically unavailable for browser SPAs.
| Mechanism | Where it fits | Why |
|---|---|---|
| DPoP | Public clients: SPAs, mobile apps, CLIs — anywhere a client certificate is unavailable. | Works at the application layer and passes through any CDN or proxy without certificate forwarding. |
| mTLS-bound | B2B integrations, service-to-service calls, financial APIs with managed clients. | No per-request cryptography; fits naturally onto a service mesh and existing PKI. |
| Both are valid | The FAPI 2.0 Security Profile mandates sender-constrained tokens and accepts either mechanism. | Plain Bearer tokens are no longer considered sufficient for high-stakes APIs in the financial profile. |
Request signing: HMAC and HTTP Message Signatures
A token proves who is calling the API; a request signature proves what exactly they sent: the method, path, headers, and body were not altered along the way. The reference HMAC scheme is AWS Signature Version 4.
HMAC signing, modeled on AWS SigV4
- Canonicalization. The request is reduced to a single possible form: method, URI-encoded path, sorted query string, canonical headers (lowercase, sorted, no extra whitespace), the signed-headers list, and the SHA-256 hash of the body. Any ambiguity here is a future "signature mismatch".
- String to sign. The canonical request is folded into a string: the
AWS4-HMAC-SHA256algorithm, an ISO 8601 timestamp, the credential scopeYYYYMMDD/region/service/aws4_request, and the hash of the canonical request. - Derived key. The secret never signs directly: an HMAC-SHA256 chain ("AWS4"+secret → date → region → service →
aws4_request) yields a key scoped to a day and a service — leaking a signature does not expose the long-lived secret. - Header. The
Authorizationheader carries the algorithm,Credential(key + scope),SignedHeaders, andSignature. The server repeats every step and compares the result; clock skew beyond 15 minutes means aRequestTimeTooSkewedrejection.
Protects against: request forgery and tampering; the timestamp in the signature bounds the replay window; the derived key limits the blast radius of a leak.
Cost: canonicalization is brittle — proxies rewrite headers and normalize URLs, debugging "signature mismatch" is miserable, and the shared secret must be protected on both sides.
HTTP Message Signatures (RFC 9421) — the standardization
- Two header fields:
Signature-Inputlists the covered components and signature parameters,Signaturecarries the value itself. - Only explicitly chosen components are signed: derived components
@method,@target-uri,@authority,@path,@query,@status— plus any HTTP header by name. - Signature parameters:
created,expires,keyid,alg,nonce,tag— the replay window and key identification are built into the standard. - Algorithms: RSASSA-PSS, ECDSA P-256/P-384, EdDSA (Ed25519), and HMAC-SHA256 — asymmetric signatures remove the problem of a shared secret living on both sides.
- Why a standard: instead of a zoo of ad-hoc schemes (SigV4 clones, draft-cavage), a signature that survives benign proxy transformations because it covers only explicitly selected components.
Replay protection and idempotency
Replaying an intercepted request is an attack; a client repeating a request after a timeout is normal. The two mechanisms below tell these cases apart and work as a pair.
Timestamp + nonce window
- The client puts a timestamp and a unique single-use nonce into the signed part of the request.
- The server rejects requests outside the acceptance window (typically minutes, adjusted for clock skew); for AWS SigV4 it is 15 minutes.
- Within the window the server remembers seen nonces (a cache with a TTL equal to the window) and rejects repeats. The window bounds storage size: nonces never need to be kept forever.
- In a cluster the nonce store must be shared across all replicas — otherwise the attacker replays the request through a neighboring replica that has not seen the nonce yet.
Cost: a shared nonce store on the path of every request, false rejections under clock skew, and the window width is a trade-off between storage size and slack for slow clients.
Idempotency keys — the adjacent mechanism
- The client generates an idempotency key (usually a UUID) per business operation and sends it in the
Idempotency-Keyheader, which an IETF httpapi draft standardizes. - The server atomically (check-and-set in one operation) stores the key together with the result of the first execution — status code and body, errors included. A repeat with the same key returns the stored result without re-executing the operation.
- Keys are short-lived: Stripe, whose implementation inspired the IETF draft, prunes keys older than 24 hours.
- The contrast with anti-replay: replay protection rejects a repeat — that is an attack; an idempotency key returns the same result — that is a legitimate retry. Write APIs need both mechanisms at once.
Rate limiting and quotas at the API gateway
Rate limiting is usually reached for as DoS protection, but the same control also keeps the infrastructure bill down, isolates tenants from one another, and contains large-scale scraping. The baseline algorithm on gateways is the token bucket.
Mechanism
- Token bucket: each limit key gets a bucket of capacity B tokens refilled at R tokens per second. A request takes a token; an empty bucket means 429 with
Retry-After. B defines the allowed burst, R the sustained rate. - The limit key is the authenticated principal: API key,
client_id, user, tenant. On the API gateway limits stack into a hierarchy: per-user inside per-tenant inside global. - Rate limits and quotas are different controls: a limit protects the system from bursts on a horizon of seconds, a quota is a business contract over a period (day, month, pricing tier). The gateway must track both.
- On a distributed gateway, counters live either in a shared store (Redis — accurate but adds latency) or locally with background sync (fast but allows briefly exceeding the limit).
Common mistakes
- Limiting by IP: behind a mobile carrier's NAT/CGNAT one address means thousands of legitimate users, while the attacker rotates IPs. The key must be the principal; IP is only a fallback for unauthenticated traffic.
- Limiting only after authentication: the AuthN check itself (hashing, a round trip to the IdP) is an expensive operation that needs a cheap limit in front of it.
- 429 without
Retry-Afterand without client-side jitter — retries arrive as a synchronized wave, and the limiter itself breeds a retry storm. - A single global limit with no tenant isolation: a noisy neighbor eats the shared budget and everyone else suffers.
Client secrets: client_secret vs private_key_jwt
Machine clients (client credentials, backend integrations) must somehow authenticate at the token endpoint. The choice of mechanism determines what exactly leaks on compromise and how painful rotation is.
client_secret — a shared secret
- A client secret is a symmetric shared secret: it travels to the token endpoint on every call and must be stored on both sides.
- Weaknesses: a leak on either side equals compromise; the secret seeps into logs, configs, and CI; mass rotation requires coordinating with every client.
private_key_jwt — client assertion (RFC 7523)
- The client holds the private key; the authorization server knows only the public one — registered directly or published via
jwks_uri. - At the token endpoint the client sends
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearerandclient_assertion— a JWT signed with the private key (RFC 7523; OIDC calls the methodprivate_key_jwt). - In the assertion:
issandsubequalclient_id,audpoints at the authorization server, a shortexp, andjtiagainst reuse. - The server verifies the signature against the registered key. The secret never crosses the network at all, and compromising the server's database does not expose the client key — only public parts live there.
Rotation
- Secret rotation is always a two-value window: register the new secret, roll the clients over, revoke the old one. Without support for two active secrets, rotation turns into downtime.
jwks_urimakes key rotation nearly free: the client publishes the new key next to the old one and the server picks it up on its own — no coordination needed.- Every secret gets a lifetime and an expiry alert: an "eternal" client_secret in production matches a long-lived token in its risk profile.
Secure API lifecycle
Without a closed security loop, API protection decays into one-off patches after each incident. The loop below is the working version: each stage feeds inputs to the next and routes feedback back into design.
Current step
1. Design
Define trust boundaries, abuse scenarios, and security requirements before coding: who calls the API, what data flows through it, and where privilege escalation is possible.
- • STRIDE/LINDDUN across endpoint groups and business flows
- • Explicit AuthN/AuthZ model, tenant isolation, and data classification
- • Security acceptance criteria in OpenAPI/ADR
Next step
2. Build
Embed checks directly into the pipeline: code, dependencies, and secrets are validated automatically before merge and artifact promotion.
- • SAST + secret scanning + dependency/SBOM scan
- • Lint/OpenAPI checks: auth scopes, schema strictness, rate-limit hints
- • Standard middleware/libraries for auth, input validation, and signature checks
API security control matrix
| Area | Controls | Validation approach | Action on failure |
|---|---|---|---|
| Identity and Access | OAuth2/OIDC, mTLS, JWT claim checks, RBAC/ABAC. | AuthN/AuthZ test suites and forbidden-path integration tests. | Block request, log policy decision, alert security owner. |
| Input and Schema | OpenAPI contract validation, canonicalization, strict parsing. | Negative tests, fuzzing, and schema-drift checks in CI. | Reject payload with explicit error code and abuse signal tagging. |
| Abuse and Availability | Rate limits, WAF rules, idempotency keys, replay protection. | Load/adversarial testing, bot simulation, replay drills. | Throttle or block source and auto-escalate repeated patterns. |
| Sensitive Data | Field-level masking, minimization, encryption in transit and at rest. | Log inspection checks, DLP scans, and PII-safe response snapshots. | Mask output, quarantine endpoint, open compliance incident. |
| Observability and Response | Audit logs, security metrics, trace correlation with tenant context. | Incident playbook drills, detection-rule validation, MTTR reviews. | Activate containment playbook and temporary hardening policies. |
Data
Data Governance & Compliance
Without data policies, the API layer becomes the primary leakage path for PII — it hands out exactly what the response returned.
Sensitive data rules for APIs
Sensitive fields are never returned by default — only what made it onto an explicit allow-list; forgetting a field hides it, which is the safe direction to fail.
Mask or simply never write PII in logs and traces: a log outlives the request and passes through more hands.
API versioning must not quietly weaken the security guarantees older clients rely on — otherwise an upgrade becomes a hidden protection regression.
Public and internal APIs are different trust levels: they require different security baselines and different access keys.
Error text must not reveal internals — service names, DB structure, or policy rules: that is free reconnaissance for an attacker.
Operational metrics
Unauthorized request block rate
Target: 100%
Confirms deny-by-default behavior for unauthenticated and invalid requests.
P95 security validation latency
Target: < 30 ms
Security controls sit on the hot request path — they must not visibly degrade user-facing latency or the API's SLO targets.
Critical API vulnerability remediation time
Target: < 24 hours
Measures response speed for high-risk findings on public API surfaces.
Replay/abuse incident MTTR
Target: < 60 minutes
Faster containment reduces financial and reputational impact.
Rollout roadmap
Phase 1 (0-30 days)
Focus: API surface inventory
Outcome: API catalog, risk classification, and accountable owner for each public contract.
Phase 2 (30-60 days)
Focus: Mandatory baseline controls
Outcome: Unified AuthN/AuthZ policy, schema validation, and gateway rate limits.
Phase 3 (60-90 days)
Focus: Abuse resilience
Outcome: Anti-replay, anti-bot controls, telemetry correlation, and detection automation.
Phase 4 (90+ days)
Focus: Operational maturity
Outcome: Regular security game days, metric reviews, and continuous hardening backlog.
Typical antipatterns
Lean on perimeter security and skip authorization checks inside services: one breached perimeter and all internal traffic is treated as trusted.
Mix user and machine tokens without an explicit privilege model — a machine token with a human's rights walks past half the controls.
Allow retries on write endpoints without anti-replay and idempotency guards: one retry and the payment goes through twice.
Log full request bodies in production — the log becomes your longest-lived leak of sensitive data.
Disable strict schema validation to ship faster: the validation gap gets found before anyone gets around to turning the check back on.
References
- OWASP API Security Top 10
- Best Current Practice for OAuth 2.0 Security (RFC 9700)
- OAuth 2.0 Demonstrating Proof of Possession — DPoP (RFC 9449)
- OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens (RFC 8705)
- HTTP Message Signatures (RFC 9421)
- JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC 7523)
- AWS Signature Version 4: create a signed request
- FAPI 2.0 Security Profile (OpenID Foundation)
- The Idempotency-Key HTTP Header Field (IETF draft, httpapi WG)
- Stripe API: idempotent requests
- NIST API Security Guidance
Related chapters
- OWASP Top 10 in the context of System Design - Maps the risk classes; on an API surface they show up as concrete attacks — BOLA, mass assignment, and auth bypass.
- Identification, authentication and authorization - Lays the IAM foundation; until it exists, API security controls rest on half-measures and are easy to bypass.
- Zero Trust: a modern approach to architectural security - Lifts the same principles to architecture: verify every request and decide access by context, not by the fact of being inside the network.
- Secrets Management Patterns - Covers secure handling of API keys, client secrets, and token material in runtime and CI/CD.
- Data Governance & Compliance - Adds PII handling policies, auditability, and regulatory constraints for API data flows.
