Replay Attack in Feathersjs with Hmac Signatures
Replay Attack in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A replay attack occurs when an adversary intercepts a valid request and retransmits it to reproduce the original effect, such as making a payment or changing a user’s email. In FeathersJS, if you use Hmac Signatures for request authentication without additional protections, the API may treat a resent, identical request as legitimate. This risk arises because Hmac relies on a shared secret to sign a canonical representation of the request; if the server only validates the signature and does not enforce one-time use or freshness, an attacker can replay the same signed payload and headers.
FeathersJS is a framework for building REST and real-time APIs. When you implement Hmac Signatures, you typically sign a combination of HTTP method, URL path, timestamp, nonce, and the request body. If the server validates the signature but does not verify that the timestamp is within an acceptable window and that the nonce has not been seen before, a replay becomes feasible. Common root causes include missing or misconfigured timestamp/nonce validation, lack of idempotency keys for state-changing operations, and insufficient guidance in the FeathersJS hooks and service logic to detect duplicates.
Consider a payment endpoint that accepts a POST with an amount and an Hmac-signed payload. An attacker captures a valid request containing { amount: 100, currency: 'USD' } and its signature. If the server processes the request based solely on signature validity and does not enforce uniqueness constraints, the attacker can replay the same signed payload to initiate duplicate transactions. Because FeathersJS does not automatically provide replay protection for Hmac flows, developers must explicitly add safeguards such as timestamp windows, nonce tracking, and idempotency checks at the service or hook layer.
Real-world attack patterns that map to this scenario include OWASP API Top 10:2023 API1:2023 Broken Object Level Authorization when combined with weak authentication schemes, and common implementation weaknesses around missing nonce or replay-cache management. PCI-DSS and SOC2 controls also emphasize the need to prevent duplicate or replayed financial transactions. In this context, middleBrick’s scans can detect missing replay-prevention mechanisms by analyzing your OpenAPI/Swagger spec (with full $ref resolution) and runtime behavior, highlighting gaps such as absent timestamp or nonce validation in Hmac-signed endpoints.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
To mitigate replay attacks in FeathersJS when using Hmac Signatures, implement timestamp validation, nonce tracking, and idempotency at the service or hook layer. Below are concrete code examples that you can adapt to your FeathersJS application.
1. Timestamp and Nonce Validation Hook
Create a custom Feathers hook that validates the timestamp and nonce for Hmac-signed requests. This hook should run before the service method and reject requests with stale timestamps or reused nonces.
// src/hooks/hmac-validation.js
const nonceCache = new Set(); // In production, use a distributed cache like Redis
module.exports = function hmacValidationHook(options = {}) {
const { timeWindowMs = 300000 } = options; // 5 minutes default
return async context => {
const { timestamp, nonce, signature } = context.params.headers;
if (!timestamp || !nonce || !signature) {
throw new Error('Missing timestamp, nonce, or signature');
}
const now = Date.now();
const requestTime = parseInt(timestamp, 10);
if (Math.abs(now - requestTime) > timeWindowMs) {
throw new Error('Request timestamp out of allowed window');
}
if (nonceCache.has(nonce)) {
throw new Error('Nonce already used — possible replay');
}
nonceCache.add(nonce);
// Optionally prune old nonces periodically
setTimeout(() => nonceCache.delete(nonce), timeWindowMs);
return context;
};
};
2. Example Hmac Signature Verification in a Feathers Service
Ensure your service method verifies the Hmac signature using the shared secret and the canonical string that includes method, path, timestamp, nonce, and body. Below is a simplified example using crypto in Node.js.
// src/services/payments/hmac-verify.js
const crypto = require('crypto');
const SHARED_SECRET = process.env.HMAC_SHARED_SECRET;
function verifyHmac(req) {
const { timestamp, nonce, signature } = req.headers;
const payload = [
req.method.toUpperCase(),
req.path,
timestamp,
nonce,
JSON.stringify(req.body || {})
].join('\n');
const expected = crypto.createHmac('sha256', SHARED_SECRET).update(payload).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error('Invalid Hmac signature');
}
}
module.exports = function paymentsServiceHook(options) {
return async context => {
verifyHmac(context.params);
// Continue to service logic
return context;
};
};
3. Idempotency Key Pattern for State-Changing Operations
For endpoints that mutate state (e.g., POST /payments), require an idempotency key header and store processed keys with their outcomes. This prevents duplicate processing on replay.
// src/hooks/idempotency.js
const idempotencyStore = new Map(); // Replace with persistent store in production
module.exports = function idempotencyHook(options = {}) {
return async context => {
const idempotencyKey = context.params.headers['idempotency-key'];
if (!idempotencyKey) {
throw new Error('Idempotency-Key header is required');
}
if (idempotencyStore.has(idempotencyKey)) {
const cached = idempotencyStore.get(idempotencyKey);
context.result = cached.result;
context.methodResult = cached.methodResult;
context.isReplayed = true;
return context;
}
// Proceed with the operation; after success, cache the result
const result = await context.app.service(context.path).create(context.data, context.params);
idempotencyStore.set(idempotencyKey, { result, methodResult: result });
return context;
};
};
4. Registering Hooks in Your Feathers Service
Apply the hooks to your Feathers service to enforce replay protection for Hmac-signed requests.
// src/services/payments/index.js
const hooks = require('feathers-hooks-common');
const hmacValidation = require('./hooks/hmac-validation');
const idempotency = require('./hooks/idempotency');
module.exports = function setupPaymentsService(app) {
const service = app.service('payments');
service.hooks({
before: {
create: [hmacValidation(), idempotency()]
}
});
};
5. OpenAPI/Swagger Considerations
Ensure your spec documents the required headers for Hmac Signatures and idempotency. Use $ref resolution to keep definitions consistent. When middleBrick scans your spec, it can highlight missing security schemes or undefined header requirements that could leave replay gaps.
Example snippet showing required headers in the spec:
paths:
/payments:
post:
summary: Create a payment
security:
- HmacAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount:
type: number
currency:
type: string
headers:
timestamp:
description: Unix epoch milliseconds
required: true
schema:
type: integer
nonce:
description: Unique request nonce
required: true
schema:
type: string
idempotency-key:
description: Idempotency key for deduplication
required: false
schema:
type: string
signature:
description: Hmac signature of the canonical payload
required: true
schema:
type: string