npm package: @mkvisuals/hmac-simple
Description: Simple HMAC-based authentication library for Node.js with replay attack prevention
Author: michalkolar.su@gmail.com
Latest version: 0.2.0

# @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

```bash
npm install @mkvisuals/hmac-simple
```

## Usage

```typescript
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

```typescript
new HmacSimple(config: HmacConfig)
```

**Config Options:**

- `secretKey` (string, required): The shared secret key used for HMAC signature generation
- `algorithm` (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 client
- `payload`: 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 request
- `timestamp`: Timestamp from the request header
- `nonce`: Nonce from the request header
- `signature`: Signature from the request header
- `payload`: 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:

```typescript
{
  id: 'x-hmac-id',
  ts: 'x-hmac-timestamp',
  nonce: 'x-hmac-nonce',
  sig: 'x-hmac-signature',
}
```

## Complete Example

### Client (Making a Request)

```typescript
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)

```typescript
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