API Keys: The Simplest Authentication That Still Trips People Up
Simple, common, and surprisingly easy to get wrong. Here's the API key playbook.
What an API key is
An API key is a long, random string that identifies the caller of an API. The caller sends it with every request; the server looks it up, verifies it's valid, and decides what the caller can do.
It's the simplest authentication scheme that exists and it's still what most APIs ship with on day one.
How keys travel
Three common placements. In order of preference:
- HTTP header (best):
x-api-key: abc123...orAuthorization: Bearer abc123.... Doesn't get logged in URL access logs, doesn't show up in browser history, doesn't end up in referers. - Query parameter (bad):
?api_key=abc123.... Leaks into logs, browser history, and HTTP referers. Avoid unless you have no choice (some legacy clients). - Request body (rare): for POST-only APIs. Hides from URL logs but adds coupling between auth and payload.
Try it
/api/v1/protectedcurl -X GET 'https://demo.totalshiftleft.ai/api/v1/protected'Without a key, most APIs return 401 Unauthorized. Now add a header x-api-key: demo-key-123:
/api/v1/protectedcurl -X GET 'https://demo.totalshiftleft.ai/api/v1/protected'200 OK. You're in.
Good API key practices
- Long and random — at least 256 bits of entropy. UUIDs are fine;
sha256(os.urandom(32))is better. - Prefixed — so leaked keys can be identified and attributed.
sk_live_abc123...tells you which service and environment. Stripe popularised this; everyone should copy it. - Hashed at rest — store only the hash in your database. If an attacker dumps the DB, they can't use the keys. Compare on incoming requests using the hash.
- Rotatable — allow issuing a new key without breaking the old one immediately. Most APIs support two active keys per account for overlap.
- Scoped — a key for "read orders" shouldn't be able to delete users. Scopes live on the key record, checked per-endpoint.
- Rate-limited per key — prevent one leaked key from hammering you.
- Audited — log which key made which request, so a leaked key's blast radius is traceable.
Common mistakes
1. Embedding keys in mobile apps. The app ships to 10 million devices. Any of those users can extract the key with 5 minutes of reverse engineering. Mobile apps should use short-lived tokens from an auth service, not raw API keys.
2. Checking for the presence of a key, not its validity. "If x-api-key header is set, let them in." Real check: key in database AND key.is_active AND key.expires_at > now().
3. Comparing keys with ==. Vulnerable to timing attacks. Use a constant-time compare (hmac.compare_digest in Python, crypto.timingSafeEqual in Node).
4. No revocation. Key leaks and there's no way to kill it short of purging the account.
5. Rotating the master secret kills every customer. Keys should be customer-specific and rotate independently.
What to test
A complete API key test suite covers:
Happy paths
- Valid key → 200 with correct response.
- Key passed in all documented formats (header, query, body).
Negative paths
- No key → 401.
- Empty key (
x-api-key:) → 401. - Invalid key (random string) → 401.
- Expired key → 401 with a distinguishable message (so clients can re-issue).
- Revoked key → 401.
- Key with wrong scope for the endpoint → 403 (not 401 — they're authenticated, just not authorized).
- Key for a different environment (staging key on prod) → 401.
Edge cases
- Key with wrong case (if the spec says it's case-sensitive).
- Key with whitespace around it — most servers should trim; test the behavior.
- Multiple keys sent (header + query) — document which wins.
- Keys at or near the rate limit — assert 429 with
Retry-After.
Security cases
- Timing-attack check: assert the difference between "valid prefix, invalid suffix" and "invalid from char 1" is under a threshold.
- Key leaked in error messages — check 500s don't echo the key.
- Key leaked in redirect URLs — check that 302s strip auth headers.
When API keys aren't enough
Keys work great for:
- Server-to-server integrations.
- Small internal APIs with trusted callers.
- Public read-only APIs with generous rate limits.
Keys start to creak when:
- You need per-user context. Keys identify who calls, not as whom. For user auth, use JWTs or OAuth.
- Keys need to be short-lived. API keys are long-lived by nature. Short-lived → use JWTs.
- You need delegated access. "Can this third-party app read my calendar?" is OAuth territory.
- Scopes get complex. API key scopes are flat. OAuth scopes and JWT claims can encode richer permissions.
What's next
The next step up from static keys is JWT authentication — short-lived, signed, and carry user context inside the token itself.
Related lessons
JWTs pack auth and user context into a signed string. Simple on the surface, full of traps underneath.
OAuth 2.0's machine-to-machine flow. Clean, standard, and easy to test once you see the shape.
Short-lived access tokens need graceful refresh. Three common patterns, one big pitfall.