@bernierllc/validators-signature-replay
v1.2.0
Published
Primitive validator for webhook signature validation and replay attack protection
Readme
@bernierllc/validators-signature-replay
Webhook signature validation and replay attack protection utilities for Node.js applications.
Overview
Provides comprehensive security validation for webhooks and API requests by validating HMAC signatures, checking timestamp freshness, and preventing replay attacks through nonce validation.
Installation
npm install @bernierllc/validators-signature-replayFeatures
- HMAC Signature Validation - Verify cryptographic signatures using SHA256/384/512
- Timestamp Freshness - Reject old requests to prevent replay attacks
- Nonce Validation - Track and reject reused request IDs
- Comprehensive Replay Protection - All-in-one validation combining signature, timestamp, and nonce
- Timing-Safe Comparison - Prevent timing attacks on signature validation
- Automatic Cleanup - Expired nonces are automatically removed from memory
- TypeScript Support - Full type definitions included
Usage
Quick Start: Complete Replay Protection
import { validateReplayAttack, generateSignature, getCurrentTimestamp } from '@bernierllc/validators-signature-replay';
// In your webhook handler
app.post('/webhook', async (req, res) => {
const signature = req.headers['x-signature'];
const timestamp = parseInt(req.headers['x-timestamp']);
const nonce = req.headers['x-nonce'];
const payload = JSON.stringify(req.body);
const result = validateReplayAttack({
secret: process.env.WEBHOOK_SECRET,
signature,
payload,
timestamp,
nonce,
algorithm: 'sha256',
maxAgeSeconds: 300, // 5 minutes
});
if (!result.isValid) {
console.error('Validation failed:', result.errors);
return res.status(401).send('Unauthorized');
}
// Process webhook safely
await processWebhook(req.body);
res.status(200).send('OK');
});Signature Validation
import { validateSignature, generateSignature } from '@bernierllc/validators-signature-replay';
// Generate signature
const payload = JSON.stringify({ event: 'test' });
const signature = generateSignature(payload, 'secret-key', 'sha256');
// Validate signature
const result = validateSignature({
secret: 'secret-key',
signature,
payload,
algorithm: 'sha256',
});
if (result.isValid) {
console.log('Signature valid');
} else {
console.error('Invalid signature:', result.errors);
}Timestamp Validation
import { validateTimestamp, getCurrentTimestamp } from '@bernierllc/validators-signature-replay';
// Get current timestamp
const timestamp = getCurrentTimestamp(); // Unix timestamp in seconds
// Validate timestamp freshness
const result = validateTimestamp({
timestamp,
maxAgeSeconds: 300, // Reject requests older than 5 minutes
isMilliseconds: false, // Set true if timestamp is in milliseconds
});
if (!result.isValid) {
console.error('Timestamp too old:', result.errors);
}Nonce Validation
import { validateNonce, generateNonce } from '@bernierllc/validators-signature-replay';
// Generate unique nonce
const nonce = await generateNonce();
// Validate nonce (automatically tracks used nonces)
const result = validateNonce({
nonce: req.headers['x-nonce'],
ttlSeconds: 300, // How long to remember this nonce
});
if (!result.isValid) {
console.error('Replay attack detected:', result.errors);
}Custom Nonce Store Management
import { NonceStore, getGlobalNonceStore, resetGlobalNonceStore } from '@bernierllc/validators-signature-replay';
// Use global singleton store (default)
const globalStore = getGlobalNonceStore();
// Or create your own store
const customStore = new NonceStore();
customStore.addNonce('custom-nonce', 600);
// Reset global store (useful for testing)
resetGlobalNonceStore();
// Cleanup when done
customStore.destroy();API Reference
validateReplayAttack(options)
Comprehensive validation combining signature, timestamp, and nonce validation.
Parameters:
secret(string) - Secret key for HMAC signaturesignature(string) - Expected signature to validatepayload(string) - Payload that was signedtimestamp(number) - Request timestampnonce(string) - Unique request identifieralgorithm(string, optional) - HMAC algorithm (default: 'sha256')maxAgeSeconds(number, optional) - Maximum timestamp age (default: 300)isMilliseconds(boolean, optional) - Whether timestamp is in milliseconds (default: false)nonceTtlSeconds(number, optional) - Nonce TTL (default: 300)
Returns: ReplayValidationResult
isValid(boolean) - Whether all validations passederrors(string[]) - Array of error messagesmetadata(object) - Detailed validation metadata
validateSignature(options)
Validate HMAC signature with timing-safe comparison.
Parameters:
secret(string) - Secret keysignature(string) - Signature to validatepayload(string) - Payload that was signedalgorithm(string, optional) - HMAC algorithm (default: 'sha256')
Returns: SignatureValidationResult
generateSignature(payload, secret, algorithm?)
Generate HMAC signature for a payload.
Parameters:
payload(string) - Data to signsecret(string) - Secret keyalgorithm(string, optional) - HMAC algorithm (default: 'sha256')
Returns: string - Hex-encoded signature
validateTimestamp(options)
Validate timestamp freshness to prevent replay attacks.
Parameters:
timestamp(number) - Timestamp to validatemaxAgeSeconds(number, optional) - Maximum age in seconds (default: 300)isMilliseconds(boolean, optional) - Whether timestamp is in milliseconds (default: false)
Returns: TimestampValidationResult
validateNonce(options)
Validate nonce uniqueness to prevent replay attacks.
Parameters:
nonce(string) - Nonce to validatettlSeconds(number, optional) - Time to live in seconds (default: 300)
Returns: NonceValidationResult
generateNonce()
Generate cryptographically secure random nonce.
Returns: Promise - Random nonce string
Utility Functions
getCurrentTimestamp()- Get current Unix timestamp in secondsgetCurrentTimestampMs()- Get current Unix timestamp in millisecondsgetGlobalNonceStore()- Get global singleton nonce storeresetGlobalNonceStore()- Reset global nonce store (useful for testing)
Real-World Examples
GitHub Webhook Validation
app.post('/github-webhook', async (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const deliveryId = req.headers['x-github-delivery'];
const payload = JSON.stringify(req.body);
const result = validateReplayAttack({
secret: process.env.GITHUB_WEBHOOK_SECRET,
signature: signature.replace('sha256=', ''),
payload,
timestamp: Date.now() / 1000,
nonce: deliveryId,
algorithm: 'sha256',
});
if (!result.isValid) {
return res.status(401).send('Unauthorized');
}
// Process GitHub event
await handleGitHubEvent(req.body);
res.status(200).send('OK');
});Stripe Webhook Validation
app.post('/stripe-webhook', async (req, res) => {
const signature = req.headers['stripe-signature'];
const eventId = req.body.id;
const timestamp = Math.floor(Date.now() / 1000);
const payload = JSON.stringify(req.body);
const result = validateReplayAttack({
secret: process.env.STRIPE_WEBHOOK_SECRET,
signature,
payload: timestamp + '.' + payload,
timestamp,
nonce: eventId,
});
if (!result.isValid) {
return res.status(401).send('Unauthorized');
}
// Process Stripe event
await handleStripeEvent(req.body);
res.status(200).send('OK');
});Security Best Practices
- Always Use HTTPS - Signature validation doesn't prevent man-in-the-middle attacks
- Short Timestamp Windows - Default 5 minutes is recommended, adjust based on clock skew
- Unique Nonces - Use cryptographically secure random generation
- Rotate Secrets - Periodically rotate webhook secrets
- Monitor Failed Validations - Log and alert on validation failures
- Limit Request Rate - Combine with rate limiting for defense in depth
Dependencies
@bernierllc/validators-core- Core validation types and utilities@bernierllc/crypto-utils- Cryptographic utilities (optional, used for nonce generation)
Integration Status
- Logger integration: Not applicable - This is a primitive validator package with no runtime logging needs
- Docs-Suite: Ready - Full API documentation with JSDoc comments
- NeverHub integration: Not applicable - This is a primitive validator package with no service discovery needs
License
Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license.
