Post

Preventing Replay Attacks

Introduction

Replay attacks occur when an attacker captures a legitimate request and replays it to gain unauthorized access. APIs that rely on static tokens or signatures without freshness checks are especially vulnerable.

Common Replay Scenarios

  • Captured bearer tokens reused from logs or intercepted traffic.
  • Signed requests replayed within the signature validity window.
  • Webhook endpoints with predictable payload signatures.

Defense in Depth

Effective replay defense combines multiple layers.

  • Use short-lived access tokens and rotate refresh tokens.
  • Enforce request timestamps with narrow acceptance windows.
  • Require unique nonces and store them to prevent reuse.
  • Bind tokens to TLS channels or device identities.

Node.js Example: HMAC Requests with Nonce Validation

The following Node.js example validates a timestamped HMAC and tracks nonces to prevent replay.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import crypto from "crypto";

const NONCE_TTL_MS = 5 * 60 * 1000;
const nonceCache = new Map();

// Evict expired nonces periodically to prevent memory leaks
setInterval(() => {
  const now = Date.now();
  for (const [key, ts] of nonceCache) {
    if (now - ts > NONCE_TTL_MS) {
      nonceCache.delete(key);
    }
  }
}, NONCE_TTL_MS);

function validateRequest({ body, nonce, timestamp, signature, secret }) {
  const now = Date.now();
  const age = Math.abs(now - Number(timestamp));

  if (age > NONCE_TTL_MS) {
    throw new Error("Timestamp outside acceptable window");
  }

  if (nonceCache.has(nonce)) {
    throw new Error("Nonce already used");
  }

  const payload = `${timestamp}.${nonce}.${body}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error("Invalid signature");
  }

  nonceCache.set(nonce, now);
}

Handling Clock Skew

  • Allow a small skew window for distributed clients.
  • Reject requests without timestamps rather than defaulting to permissive behavior.
  • Evict old nonces aggressively to limit memory growth.

Conclusion

Replay prevention is not a single feature. Combine time-bound tokens, nonce tracking, and cryptographic signatures to ensure every request is unique and time-bound.

This post is licensed under CC BY 4.0 by the author.