SHA1Generator

How to Use HMAC Correctly in Modern APIs

SHA1Generator Team
9 min read
HMACAPI SecurityRequest SigningBest Practices

HMAC (Hash-Based Message Authentication Code) helps you verify that a request is genuine and unchanged. This guide explains a simple, correct way to use HMAC in modern APIs: build a stable request string, use SHA‑256 or SHA‑512, include a timestamp and nonce, and verify all of it on the server.

What Is HMAC and Why Use It?

HMAC mixes a shared secret with a hash function (such as SHA‑256) to create a signature for your message. It resists tampering and proves the sender had the secret.

  • Integrity: Catches any change to the message.
  • Authenticity: Only holders of the secret can produce the signature.
  • Performance: Fast in browsers, servers, and edge runtimes.

When to Use HMAC and the Correct Pattern

Recommended Pattern

  1. Add a timestamp and nonce (UUID) to each request.
  2. Compute a body hash (SHA‑256 of the raw body bytes).
  3. Build a canonical string: method, path, sorted query, chosen headers, body hash.
  4. Compute HMAC(secret, canonical) with SHA‑256 or SHA‑512.
  5. Send the signature, timestamp, nonce, keyId in headers.

Common Mistakes

  • Using SHA‑1 or MD5 for signing (deprecated).
  • Signing unsorted JSON or query params (results change across clients).
  • Missing replay protection (no timestamp/nonce or TTL window).
  • Leaking secrets in client code or logs.
  • Ignoring clock skew in distributed systems.

Browser Example (Web Crypto)

This example computes HMAC‑SHA‑256 with crypto.subtle and a canonical request string:

async function hmacSha256(secret, message) {
	const enc = new TextEncoder();
	const keyData = enc.encode(secret);
	const key = await crypto.subtle.importKey(
		'raw',
		keyData,
		{ name: 'HMAC', hash: 'SHA-256' },
		false,
		['sign']
	);
	const signature = await crypto.subtle.sign('HMAC', key, enc.encode(message));
	return Array.from(new Uint8Array(signature))
		.map(function (b) { return b.toString(16).padStart(2, '0'); })
		.join('');
}

function canonicalize(obj) {
	const method = obj.method;
	const path = obj.path;
	const query = obj.query || {};
	const headers = obj.headers || {};
	const bodyHash = obj.bodyHash || '';

	const qs = Object.keys(query)
		.sort()
		.map(function (k) {
			return k + '=' + encodeURIComponent(query[k]);
		})
		.join('&');

	const hs = Object.keys(headers)
		.sort()
		.map(function (k) {
			return k.toLowerCase() + ':' + String(headers[k]).trim();
		})
		.join('\n');

	return method.toUpperCase() + '\n' + path + '\n' + qs + '\n' + hs + '\n' + bodyHash;
}

// Usage
const secret = 'your-secret-key';
const nonce = crypto.randomUUID();
const timestamp = Math.floor(Date.now() / 1000);

const payload = { amount: 100, currency: 'USD' };
const bodyData = new TextEncoder().encode(JSON.stringify(payload));
const bodyHashBuffer = await crypto.subtle.digest('SHA-256', bodyData);
const bodyHashHex = Array.from(new Uint8Array(bodyHashBuffer))
	.map(function (b) { return b.toString(16).padStart(2, '0'); })
	.join('');

const canonical = canonicalize({
	method: 'POST',
	path: '/v1/payments',
	query: { a: 1 },
	headers: {
		'x-nonce': nonce,
		'x-timestamp': timestamp
	},
	bodyHash: bodyHashHex
});

const signature = await hmacSha256(secret, canonical);

console.log('Canonical String:' + canonical);
console.log('HMAC Signature: ' + signature);

Tip: Use our UUID Generator for nonces and Password Generator for strong API secrets.

Server Verification Checklist

  • Check the timestamp is inside a small window (e.g., ±300s).
  • Reject requests if the nonce was used before (store for TTL).
  • Rebuild the canonical string exactly like the client.
  • Select the right key by keyId and compute HMAC.
  • Verify with a constant‑time comparison.
  • Log pass/fail for monitoring and incident response.

Rotate keys regularly and keep old keys for a short grace period to avoid breaking clients.

Implementation Notes and Best Practices

  • Prefer SHA‑256 or SHA‑512 for HMAC; avoid SHA‑1.
  • Canonicalize query, headers, and JSON consistently to avoid signature drift.
  • Set a strict TTL window and handle clock skew with small leeway.
  • Protect secrets: use server‑side storage, environment variables, and never echo secrets in logs.
  • Version your signing scheme (e.g., x-signature-v) so you can evolve it safely.
  • Validate content‑type and compute body hashes on raw bytes to avoid normalization issues.

Canonical Request Layout Example

METHOD
PATH
key=a&x=1
content-type:application/json
x-key-id:demo
x-nonce:550e8400-e29b-41d4-a716-446655440000
x-timestamp:1731313131
BODY_SHA256_HEX

Sort keys and normalize header values to keep signatures deterministic.

Related Tools and Guides

Conclusion

HMAC is a practical way to protect APIs. Use SHA‑256 or SHA‑512, build a stable request string, and add replay protection with timestamps and nonces. These basics go a long way on browsers, servers, and edge.

Start with a clear signing spec, test with known vectors, and evolve safely with versioned headers.