How to protect APIs from replay attacks

Todd Shinders
11 Min Read

You can build a “secure” API, use TLS everywhere, validate JWTs, and still get wrecked by a replay attack.

Because replay is not about breaking crypto. It is about stealing time.

An attacker captures a legitimate request, maybe from a compromised client device, a leaked proxy log, or a malware-infected workstation, then re-sends it later. If the request is still valid and your server has no concept of “freshness,” you just authorized the same action twice. Payments get double-charged. Password resets get re-triggered. Inventory reservations get duplicated. Rate limits do not always save you, because the attacker is not spamming, they are reusing your own valid traffic.

Replay attacks are simple: a valid request is recorded and then repeated to produce the same effect. Your job is to make each request either (1) expire quickly, (2) be usable only once, or (3) be cryptographically bound to the sender so a captured token cannot be reused by someone else.

Here is how to do it in a way that holds up in production.

Why replay attacks slip through “normal” API security

Most API stacks focus on identity: API keys, OAuth access tokens, JWT validation, mTLS. Replay exploits a different gap: authorization without uniqueness.

If your server accepts “any request with a valid bearer token,” then anyone holding that token can replay it until it expires. This is why newer OAuth guidance has pushed sender-constrained tokens and proof-of-possession techniques that can detect or prevent token replay.

Even if you use short-lived tokens, replay windows still exist. A 5-minute token is a 5-minute attack window. If your API supports high-impact actions (money movement, account recovery, permission changes), you need stronger guarantees than “token not expired.”

OWASP’s API Security guidance is also clear about a bigger picture reality: API risk is not just authn and authz, it’s the operational mechanics around your endpoints, and attackers target the seams.

What experts keep emphasizing about replay defenses

When you read the standards and vendor guidance back-to-back, the theme is consistent: you cannot “bolt on” replay protection with a single header. You need a coherent strategy.

David Waite, standards author, Ping Identity, and co-authors describe DPoP as a way to sender-constrain OAuth tokens so a stolen token is less useful, specifically calling out replay detection and mitigation for access and refresh tokens.

See also  The complete guide to debugging production issues

Okta’s resource server guidance explains DPoP in practical terms: the client proves possession of a key each request, binding token use to that client and reducing token replay across endpoints.

OWASP’s API Security project focuses more broadly on the most critical API risks and the idea that API security needs systematic controls, not endpoint-by-endpoint heroics. Replay defenses fit that mindset because they require consistent validation, clock discipline, and storage for used nonces or request IDs.

The synthesis: aim for at-least-once detection with low false positives, then layer cryptographic sender binding where it matters.

Choose the right replay defense for your API

You have a menu of defenses. The best choice depends on whether your clients can safely hold keys, whether requests can be signed, and whether you can store per-request state.

Technique Stops replay? State required server-side Best for
Short-lived tokens only Reduces window None Low-risk endpoints
Timestamp + request ID (nonce) Yes, within window Yes (nonce cache) Most internal APIs
Idempotency keys (per operation) Yes, for duplicates Yes (idempotency store) Payments, create operations
Request signing (HMAC) Yes, within window Often (nonce) Server-to-server APIs
Proof-of-possession (DPoP) Detects token replay, constrains tokens Sometimes (jti tracking) Public clients, OAuth APIs

DPoP is explicitly designed to help with replay of OAuth tokens by binding use to a client-held key.

Classic nonce and timestamp strategies remain a workhorse for many APIs, especially service-to-service traffic, because they are straightforward to implement and audit.

Implement replay protection in 4 steps

Step 1: Define what “fresh” means per endpoint

Not every endpoint needs the same replay guarantees. Split endpoints into tiers:

  • Tier 0: read-only, low impact

  • Tier 1: writes that are safe to retry idempotently

  • Tier 2: high impact, money or identity, must be replay-resistant

For Tier 2, you want a tight time window (often 30 to 120 seconds) and one-time request semantics.

Step 2: Add a timestamp and a nonce, then enforce both

The pattern looks like this:

  • Client sends ts (Unix time) and nonce (random, unique per request)

  • Server checks abs(now - ts) <= window_seconds

  • Server checks nonce has not been seen before for that client within the window

  • Server stores nonce with TTL equal to the window

See also  Influence isn’t about architecture — it’s about alignment

This is the simplest replay defense that works across languages and API gateways. It is also where teams make mistakes:

  • They validate timestamps but never store nonces, so attackers replay within the window.

  • They store nonces globally, causing collisions across clients.

  • They store nonces forever, turning the cache into a database.

Nonce-plus-timestamp is widely recommended as a general replay mitigation technique.

Step 3: Require request signing for anything you would hate to see replayed

Freshness checks are stronger when the nonce and timestamp are included in a signature. Otherwise an attacker can tamper with them.

Two common options:

  • HMAC signing for server-to-server traffic (shared secret per client).

  • Asymmetric signing for public clients (client holds private key, server verifies).

In both cases, you sign a canonical representation of method, path, headers, body hash, timestamp, and nonce. That signature makes the request self-authenticating and makes replay detection reliable because the server can trust the freshness fields.

This is the same conceptual direction as DPoP in OAuth, where the client signs a proof that binds a token’s use to that client.

Step 4: Make high-impact POSTs idempotent on purpose

Replay defense is about preventing unauthorized repeats. Idempotency is about making repeats safe, including legitimate retries.

For endpoints like POST /charges or POST /orders, require an Idempotency-Key header, then store the first successful response keyed by (client_id, idempotency_key) for a bounded period. If you see the same key again, return the same result.

This does two things:

  1. Retries become safe and predictable.

  2. “Accidental replay” becomes a non-event.

Combine idempotency with nonce and signatures for Tier 2 actions and you get a system that is hard to abuse and easy to reason about.

Operational guardrails that make replay protection stick

Replay protection fails most often due to operational drift, not bad crypto.

Clock skew is the big one. If clients’ clocks vary wildly, you either reject good requests or widen your time window and weaken protection. You can mitigate this by:

  • Using NTP on servers, and strongly encouraging it on managed clients.

  • Returning server time in error responses so clients can adjust.

  • Keeping the freshness window tight for high-risk endpoints, and looser for low-risk ones.

See also  Event-driven architecture explained (and when to use it)

Also, monitor the right signals:

  • Rate of “nonce already used” rejections

  • Rate of “timestamp outside window” rejections

  • Top offending client IDs and IP ranges

  • Correlation with deploys, clock changes, or mobile app releases

If you are fronted by an API gateway, remember the gateway can help with throttling and WAF controls, but replay protection typically requires application-level semantics and state. AWS’s API Gateway guidance highlights layered controls like WAF and throttling, but you still need endpoint-specific logic for issues like replay.

FAQ

Is TLS enough to prevent replay attacks?

No. TLS protects data in transit, but if a valid request is captured from a compromised endpoint or logs, it can be replayed unless your API enforces freshness and uniqueness.

Should I store every nonce forever?

No. Store nonces only within the allowed time window, using a TTL cache. Storing forever creates an unbounded database and does not add much security.

When should I use DPoP?

DPoP is a strong option when you use OAuth 2.0 and want to reduce the value of stolen bearer tokens by binding token use to a client key. It is explicitly designed to address token replay concerns.

What if my clients cannot safely hold signing keys?

Then focus on short token lifetimes, strict scopes, strong anomaly detection, and server-side idempotency. You can still implement nonce and timestamp checks, but treat the client as less trustworthy.

Honest Takeaway

Replay protection is not a single feature, it is a posture. You define freshness, enforce one-time use where it matters, and bind requests to a sender when you can. The winning combo for most serious APIs is timestamp + nonce + signature, plus idempotency keys for create and payment-like operations.

If you only do one thing this week, do this: pick your Tier 2 endpoints and make them replay-resistant first. That is where the damage happens, and it is where attackers will try the simplest trick in the book, using your own valid requests against you.

Share This Article
Todd is a news reporter for Technori. He loves helping early-stage founders and staying at the cutting-edge of technology.