@mkvisuals/hmac-simple
v0.2.0
Published
Simple HMAC-based authentication library for Node.js with replay attack prevention
Maintainers
Readme
@mkvisuals/hmac-simple
Simple HMAC-based authentication library with built-in replay attack prevention.
Features
- HMAC-based request signing and verification
- Works in both Node.js and browsers
- Replay attack prevention with nonce tracking
- Timestamp validation
- Constant-time signature comparison to prevent timing attacks
- Automatic cleanup of expired nonces
- TypeScript support with full type definitions
- Both ESM and CommonJS support
- Tiny bundle size (~4.5KB ESM, ~3KB CJS)
Installation
npm install @mkvisuals/hmac-simpleUsage
import { HmacSimple, HMAC_HEADERS } from '@mkvisuals/hmac-simple';
// Initialize with a shared secret key
const hmac = new HmacSimple({
secretKey: 'your-secret-key-here',
algorithm: 'sha256', // optional, default: 'sha256'
timestampToleranceMs: 5 * 60 * 1000, // optional, default: 5 minutes
nonceMaxAgeMs: 30 * 60 * 1000, // optional, default: 30 minutes
});
// Client: Create headers for an outgoing request (async)
const clientId = 'client-123';
const requestBody = JSON.stringify({ data: 'example' });
const headers = await hmac.createHeaders(clientId, requestBody);
console.log(headers);
// {
// 'x-hmac-id': 'client-123',
// 'x-hmac-timestamp': '1234567890',
// 'x-hmac-nonce': 'abc123...',
// 'x-hmac-signature': 'def456...'
// }
// Server: Verify headers from an incoming request (async)
const result = await hmac.verifyHeaders(
headers[HMAC_HEADERS.id],
headers[HMAC_HEADERS.ts],
headers[HMAC_HEADERS.nonce],
headers[HMAC_HEADERS.sig],
requestBody
);
if (result.valid) {
console.log('Request authenticated successfully');
} else {
console.error('Authentication failed:', result.error);
}API Reference
HmacSimple
Constructor
new HmacSimple(config: HmacConfig)Config Options:
secretKey(string, required): The shared secret key used for HMAC signature generationalgorithm(string, optional): Hash algorithm to use (default: 'sha256')timestampToleranceMs(number, optional): Maximum allowed time difference in milliseconds (default: 300000 - 5 minutes)nonceMaxAgeMs(number, optional): How long to track nonces in milliseconds (default: 1800000 - 30 minutes)
Methods
async createHeaders(clientId: string, payload?: string): Promise<Record<string, string>>
Creates HMAC authentication headers for a request.
Parameters:
clientId: Unique identifier for the clientpayload: Optional request payload (typically JSON stringified body)
Returns: Promise resolving to an object containing HMAC headers
async verifyHeaders(clientId: string, timestamp: string, nonce: string, signature: string, payload?: string): Promise<HmacVerificationResult>
Verifies HMAC authentication headers from a request.
Parameters:
clientId: Client identifier from the requesttimestamp: Timestamp from the request headernonce: Nonce from the request headersignature: Signature from the request headerpayload: Optional request payload to verify
Returns: Promise resolving to a verification result object with valid boolean and optional error message
getNonceCount(): number
Returns the current number of tracked nonces (useful for monitoring).
destroy(): void
Cleans up resources by clearing all tracked nonces.
HMAC_HEADERS
Object containing header name constants:
{
id: 'x-hmac-id',
ts: 'x-hmac-timestamp',
nonce: 'x-hmac-nonce',
sig: 'x-hmac-signature',
}Complete Example
Client (Making a Request)
import { HmacSimple } from '@mkvisuals/hmac-simple';
const hmac = new HmacSimple({ secretKey: 'shared-secret' });
const clientId = 'client-123';
async function makeAuthenticatedRequest() {
const body = JSON.stringify({ action: 'getData' });
const headers = await hmac.createHeaders(clientId, body);
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers,
},
body,
});
return response.json();
}Server (Verifying a Request)
import express from 'express';
import { HmacSimple, HMAC_HEADERS } from '@mkvisuals/hmac-simple';
const app = express();
const hmac = new HmacSimple({ secretKey: 'shared-secret' });
app.use(express.json());
app.post('/data', async (req, res) => {
const clientId = req.headers[HMAC_HEADERS.id];
const timestamp = req.headers[HMAC_HEADERS.ts];
const nonce = req.headers[HMAC_HEADERS.nonce];
const signature = req.headers[HMAC_HEADERS.sig];
const payload = JSON.stringify(req.body);
const result = await hmac.verifyHeaders(clientId, timestamp, nonce, signature, payload);
if (!result.valid) {
return res.status(401).json({ error: result.error });
}
// Request is authenticated
res.json({ success: true, data: 'your data here' });
});
app.listen(3000);Security Considerations
- Secret Key: Keep your secret key secure and never commit it to version control
- HTTPS: Always use HTTPS in production to prevent man-in-the-middle attacks
- Payload Verification: Always include the request payload in the signature to prevent tampering
- Time Synchronization: Ensure server clocks are synchronized (NTP) for timestamp validation
- Nonce Storage: For distributed systems, consider using a shared cache (Redis) for nonce tracking
License
MIT
