HMAC signature authentication signs the request itself with a shared secret key, simultaneously proving the sender's identity and the request's integrity (that it was not tampered with). AWS Signature V4 is the canonical example; it is used for payments, webhooks, and server-to-server APIs where preventing tampering and replay matters.
Authorization: <scheme> Credential=..., Signature=... (+ signed headers, timestamp, nonce)The client and server share a secret key. The client builds a canonical form of the request (method, path, sorted headers, timestamp, body hash, and so on) into a single string-to-sign, signs it with HMAC-SHA256, and places the result in the Authorization header. The server reconstructs the same string with the same secret, recomputes the signature, and checks equality with a constant-time comparison. The secret key itself never travels over the network.
Whereas a bearer token 'passes as long as you have the token' and is thus vulnerable to theft, HMAC binds each field of the request into the signature, so changing even one parameter in transit breaks it. Including a timestamp and a nonce (one-time value) in the signature further defeats replay attacks that resend a past request verbatim. The server typically accepts a request only if the timestamp is within ±5 minutes of now and the nonce has never been seen before.
The upside is that the credential (secret key) is never transmitted and the whole request is signed and protected; the downside is that both client and server must implement exactly the same canonicalization rules, which is finicky. A tiny mismatch in header ordering, encoding, or body hashing makes the signatures disagree.
Authorization: AWS4-HMAC-SHA256 Credential=AKIA.../20260716/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024import hmac, hashlib, time
ts = str(int(time.time()))
body = request_body
msg = f'{method}\n{path}\n{ts}\n{hashlib.sha256(body).hexdigest()}'
sig = hmac.new(SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest()
headers = {'X-Timestamp': ts, 'Authorization': f'HMAC keyId={KEY_ID}, signature={sig}'}import crypto from 'crypto';
const expected = crypto.createHmac('sha256', SECRET)
.update(`${method}\n${path}\n${ts}\n${bodyHash}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
if (!ok || Math.abs(Date.now()/1000 - ts) > 300) res.status(401).end();