iph-replay-guard
v1.0.3
Published
A lightweight reusable Express.js middleware for protecting APIs against replay attacks using nonce and timestamp validation.
Readme
iph-replay-guard
A lightweight reusable Express.js middleware for protecting APIs against replay attacks using nonce and timestamp validation.
Overview
iph-replay-guard is an Express middleware package designed to prevent replay attacks in APIs and secure communication systems.
The middleware validates:
- request nonce values
- request timestamps
- duplicate request reuse within a configurable time window
It is especially useful for:
- signed APIs
- encrypted payload APIs
- payment systems
- webhook protection
- mobile applications
- device-authenticated APIs
What Is a Replay Attack?
A replay attack occurs when an attacker captures a valid request and resends it later to repeat an action.
Examples:
- duplicate payment requests
- repeated transaction submissions
- replayed authenticated API calls
- reused signed requests
Replay attacks can occur even when requests are encrypted or signed.
How iph-replay-guard Works
The middleware validates three things:
- a nonce header
- a timestamp header
- whether the nonce was already used
If:
- the timestamp is too old
- the nonce already exists
- required headers are missing
the request is blocked.
Features
Replay Protection
Blocks duplicate requests using unique nonce tracking.
Timestamp Validation
Rejects requests outside an allowed time window.
Pluggable Nonce Storage
Supports:
- memory storage
- Redis
- databases
- custom distributed stores
Route Skipping
Supports:
- custom skip functions
- multiple skipped paths
Lightweight
Minimal runtime overhead and easy Express integration.
Installation
npm install iph-replay-guardBasic Usage
import express from "express";
import {
createMemoryNonceStore,
createReplayProtection,
} from "iph-replay-guard";
const app = express();
const store = createMemoryNonceStore();
app.use(
createReplayProtection({
store,
}),
);Request Requirements
Clients must send:
x-noncex-timestamp
Example Request
POST /api/orders
x-nonce: 8d95a83f-4f22-42c8-a1de-2c83f34e749e
x-timestamp: 1770000000000How Nonces Work
A nonce is a unique value generated for every request.
Rules:
- must be unique per request
- should never repeat
- typically generated using UUIDs
Example:
8d95a83f-4f22-42c8-a1de-2c83f34e749eMiddleware Order
Recommended middleware order:
app.use(express.json());
app.use(replayProtection);
app.use(routes);Place replay protection:
- before protected routes
- before request decryption logic
- before sensitive business logic
API
createReplayProtection(options)
Creates and returns the replay-protection middleware.
createMemoryNonceStore()
Creates a simple in-memory nonce store.
Recommended only for:
- local development
- testing
- single-instance applications
Type Definitions
export type NonceStore = {
hasSeen(nonce: string): Promise<boolean>;
mark(
nonce: string,
expiresAt: Date,
): Promise<boolean>;
};
export type ReplayProtectionOptions = {
skip?: (req: Request) => boolean;
skipPaths?: string[];
timestampHeader?: string;
nonceHeader?: string;
maxAgeSeconds?: number;
store: NonceStore;
};Options
store
Type:
NonceStoreRequired nonce storage implementation.
The store is responsible for:
- checking whether a nonce exists
- storing new nonces
- handling expiration
skip
Type:
(req: Request) => booleanCustom logic for skipping replay protection.
Example:
createReplayProtection({
store,
skip: (req) =>
req.method === "GET",
});skipPaths
Type:
string[]List of paths excluded from replay protection.
Example:
createReplayProtection({
store,
skipPaths: [
"/health",
"/metrics",
"/api/payments/webhook",
],
});timestampHeader
Type:
stringTimestamp header name.
Default:
x-timestampnonceHeader
Type:
stringNonce header name.
Default:
x-noncemaxAgeSeconds
Type:
numberMaximum allowed request age.
Default:
300(5 minutes)
Multiple Route Skipping Example
app.use(
createReplayProtection({
store,
skipPaths: [
"/health",
"/metrics",
"/api/payments/webhook",
],
}),
);Conditional Skipping Example
app.use(
createReplayProtection({
store,
skip: (req) =>
req.method === "GET" ||
req.path.startsWith("/public"),
}),
);Custom Nonce Store Example
const store = {
async hasSeen(nonce: string) {
return false;
},
async mark(
nonce: string,
expiresAt: Date,
) {
return true;
},
};
app.use(
createReplayProtection({
store,
}),
);Redis Store Example
const store = {
async hasSeen(nonce: string) {
return Boolean(
await redis.get(nonce),
);
},
async mark(
nonce: string,
expiresAt: Date,
) {
const ttlSeconds =
Math.ceil(
(
expiresAt.getTime() -
Date.now()
) / 1000,
);
await redis.set(
nonce,
"1",
"EX",
ttlSeconds,
);
return true;
},
};Response Status Codes
| Status | Meaning | | ------ | ---------------------------- | | 400 | Missing replay headers | | 408 | Timestamp expired | | 409 | Replay request detected | | 500 | Internal nonce-store failure |
Example Error Responses
Missing Headers
{
"success": false,
"message": "Missing replay protection headers"
}Expired Timestamp
{
"success": false,
"message": "Request timestamp outside allowable window"
}Replay Detected
{
"success": false,
"message": "Replay request detected"
}Internal Flow
The middleware performs the following steps:
- checks skipped routes
- reads nonce header
- reads timestamp header
- validates request age
- checks nonce existence
- stores nonce
- forwards request
Security Recommendations
Use HTTPS
Replay protection should always be used with HTTPS.
Without HTTPS:
- attackers can intercept requests
- headers can be captured
- replay attacks become easier
Use Cryptographically Secure Nonces
Recommended:
- UUID v4
- crypto.randomUUID()
- secure random generators
Avoid:
- sequential IDs
- timestamps alone
- predictable strings
Use Distributed Storage
For production systems with multiple servers:
- Redis
- Memcached
- distributed databases
Avoid memory stores in clustered deployments.
Performance Notes
The middleware is lightweight.
Operations performed:
- header parsing
- timestamp comparison
- nonce lookup
- nonce insertion
Performance depends mainly on the storage backend.
Best Practices
- place middleware early in the request chain
- combine with request signing
- combine with payload encryption
- use short replay windows
- use distributed nonce storage
- rotate secrets regularly
Recommended Security Stack
Recommended production setup:
HTTPS
↓
Replay Protection
↓
HMAC Verification
↓
Payload Decryption
↓
Authentication
↓
Application RoutesLimitations
Memory Store Limitations
The memory store:
- is process-local
- resets on restart
- does not work across multiple servers
Clock Synchronization
Replay protection depends on timestamps.
Ensure:
- servers use NTP
- client clocks are reasonably accurate
Example Full Setup
import express from "express";
import {
createMemoryNonceStore,
createReplayProtection,
} from "iph-replay-guard";
const app = express();
const store = createMemoryNonceStore();
app.use(express.json());
app.use(
createReplayProtection({
store,
maxAgeSeconds: 300,
skipPaths: [
"/health",
"/api/payments/webhook",
],
}),
);
app.listen(3000);License
MIT
Author
Published by:
Prashant Srivastav
Package
https://www.npmjs.com/package/iph-replay-guard
