Why our public API uses HMAC signing, not JWTs
If you skim the KeyStack docs the first thing that jumps out is the public API doesn't use plain Authorization: Bearer <token>. Instead, every request must include a timestamp and an HMAC-SHA256 signature.
Why?
1. JWTs leak too easily
A bearer token in the URL bar, in a log file, in an error report, in a third-party SDK — that's a full compromise. HMAC signatures are computed per-request, so even if the signature leaks, it's only valid for ~5 minutes against the exact body it was generated for.
2. Replay protection comes free
Pair the timestamp with a Redis nonce store and you have replay protection without inventing a separate nonce scheme. We give you 5 minutes of clock skew tolerance and remember every signature for 10 minutes. Send the same request twice — 409 api/timestamp-replay.
3. Rotation without downtime
A leaked API secret can be rotated in seconds. Old clients keep working until you flip the kill switch; new clients pick up the new secret on next request. JWTs require a longer dance with refresh tokens and denylists.
4. It costs you ~5 lines of code
const sig = crypto
.createHmac('sha256', secret)
.update(`${ts}.${body}`)
.digest('hex');That's it. Compared to "set up JWT issuer, rotate keys, handle exp, handle nbf, distribute JWKS endpoint" the operational cost is trivial.
When we do use JWTs
For dashboard sessions where:
- The client is a browser (so timestamp skew is a real headache).
- The traffic is one-tenant-at-a-time (so rotation cost is low).
- You want short-lived access tokens + refresh tokens to keep "log everyone out" cheap.
There, JWTs are fantastic. They're just not the right tool for a public API hit by every install of your shipping product.
If you've made it this far and you're licensing software, you should probably be using KeyStack. Sign up — it's free for hobby projects.