System Design Space
Knowledge graphSettings

Updated: June 25, 2026 at 5:45 AM

API Security Patterns

medium

Practical API security patterns: authn/authz, rate limiting, schema validation, anti-replay, abuse prevention and secure API lifecycle.

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.

Open chapter

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.

Open RFC 9449

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 9449

How it works

  1. 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.
  2. For every request the client creates a DPoP proof — a compact JWT with header typ: "dpop+jwt" and the public key in jwk, signed with the private key.
  3. 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, also ath — the SHA-256 hash of the access token.
  4. The authorization server embeds the confirmation cnf.jkt into the token — the base64url JWK SHA-256 thumbprint of the public key. The token is sent as Authorization: DPoP <token>, not Bearer.
  5. The resource server validates the proof signature, checks that htm/htu match the actual request, that iat/jti are fresh, and that jkt equals the key from the proof.
  6. Against pre-generated proofs the server issues a value in the DPoP-Nonce header and responds with the use_dpop_nonce error; 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 8705

How it works

  1. 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) or self_signed_tls_client_auth (the key registered via jwks).
  2. The server embeds the confirmation cnf with the x5t#S256 member into the issued token — the base64url SHA-256 hash of the DER encoding of the client certificate.
  3. Every API call also runs over mTLS: the resource server takes the certificate from the TLS layer and compares its hash with the x5t#S256 value in the token.
  4. If the hashes do not match, the request is rejected with 401 and the invalid_token error 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.

MechanismWhere it fitsWhy
DPoPPublic 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-boundB2B 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 validThe 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

  1. 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".
  2. String to sign. The canonical request is folded into a string: the AWS4-HMAC-SHA256 algorithm, an ISO 8601 timestamp, the credential scope YYYYMMDD/region/service/aws4_request, and the hash of the canonical request.
  3. 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.
  4. Header. The Authorization header carries the algorithm, Credential (key + scope), SignedHeaders, and Signature. The server repeats every step and compares the result; clock skew beyond 15 minutes means a RequestTimeTooSkewed rejection.

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-Input lists the covered components and signature parameters, Signature carries 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

  1. The client puts a timestamp and a unique single-use nonce into the signed part of the request.
  2. The server rejects requests outside the acceptance window (typically minutes, adjusted for clock skew); for AWS SigV4 it is 15 minutes.
  3. 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.
  4. 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-Key header, 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-After and 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)

  1. The client holds the private key; the authorization server knows only the public one — registered directly or published via jwks_uri.
  2. At the token endpoint the client sends client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer and client_assertion — a JWT signed with the private key (RFC 7523; OIDC calls the method private_key_jwt).
  3. In the assertion: iss and sub equal client_id, aud points at the authorization server, a short exp, and jti against reuse.
  4. 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_uri makes 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.

Secure APIlifecycle loop1. Designthreat model + API contract2. Buildsecure implementation3. Verifysecurity tests + release gates4. Deploygateway + policy enforcement5. Runtelemetry + incident response

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

AreaControlsValidation approachAction on failure
Identity and AccessOAuth2/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 SchemaOpenAPI 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 AvailabilityRate limits, WAF rules, idempotency keys, replay protection.Load/adversarial testing, bot simulation, replay drills.Throttle or block source and auto-escalate repeated patterns.
Sensitive DataField-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 ResponseAudit 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.

Open chapter

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

1

Phase 1 (0-30 days)

Focus: API surface inventory

Outcome: API catalog, risk classification, and accountable owner for each public contract.

2

Phase 2 (30-60 days)

Focus: Mandatory baseline controls

Outcome: Unified AuthN/AuthZ policy, schema validation, and gateway rate limits.

3

Phase 3 (60-90 days)

Focus: Abuse resilience

Outcome: Anti-replay, anti-bot controls, telemetry correlation, and detection automation.

4

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

Related chapters

Enable tracking in Settings