The Question
You're building an API. How do callers prove who they are? The choice depends on who the callers are (humans, machines, both), how sensitive the data is, and how much complexity you can afford.
Here are the major patterns, ranked roughly from simplest to most secure.
1. API Keys
The simplest. The server issues a long random string. The caller sends it on every request:
GET /api/v1/users
Authorization: Bearer ak_live_abc123...
The server looks up the key in a database, identifies the caller, applies their permissions.
Pros: trivial to implement and use. Easy to generate, rotate, revoke.
Cons: the key is the credential. If leaked, attacker has full access until revoked. No proof of who's actually using it.
Use when: server-to-server APIs with low to medium sensitivity. Most public APIs (Stripe, Twilio, OpenAI) use this for production keys.
2. Basic Auth
Username + password sent in every request, base64-encoded. Old school. Still around because it's simple.
Pros: universally supported. No state required.
Cons: credentials in every request. Must be over HTTPS or it's a disaster. Hard to rotate without coordinating between client and server.
Use when: internal tools, legacy systems, or when nothing else is available.
3. JWT Bearer Tokens
The user authenticates once, receives a JWT, sends it on every request. Server verifies the signature and trusts the contents.
POST /login
{"username": "alice", "password": "..."}
-> Response: {"token": "eyJhbGc...."}
GET /api/v1/profile
Authorization: Bearer eyJhbGc....
Pros: stateless, no database lookup per request, contains user info.
Cons: revocation is hard. See the JWT article.
Use when: user-facing APIs where stateless authentication scales better than session storage.
4. OAuth 2.0
For "user authorizes a third-party app to act on their behalf." Covered in detail in the OAuth article.
Use when: you're letting third-party apps access user data (Google APIs, Stripe Connect). Or when you want to outsource auth to an identity provider (Google, Auth0, Okta).
5. HMAC Request Signing
The caller has an API key AND a secret. Each request is signed with the secret. The server verifies the signature.
signature = HMAC-SHA256(
secret,
method + path + body + timestamp
)
Headers:
X-API-Key: ak_abc123
X-Timestamp: 1714896000
X-Signature: 8f4d2a...
Why? Even if a request is captured in transit, attackers can't replay it (timestamp validation) or modify it (signature would change). The secret never travels over the wire.
Pros: very secure. Used by AWS, Twilio webhooks, financial APIs.
Cons: more complex client implementation. Time skew between client and server can cause failures.
Use when: high-value APIs, server-to-server, when you can't fully trust the network or want extra protection.
6. Mutual TLS (mTLS)
Both client AND server present TLS certificates. Each verifies the other.
Pros: very strong authentication. Identity baked into the connection. No tokens to leak.
Cons: certificate management is operationally complex. Issuing, rotating, revoking client certs at scale is hard.
Use when: internal microservices, financial integrations, sensitive B2B APIs. Also common in service meshes (Istio, Linkerd).
7. Session Cookies
Old-school web app pattern. User logs in, server creates a session, sends back an httpOnly cookie containing a session ID.
Pros: battle-tested. Easy revocation (delete the session). Browsers handle it.
Cons: requires server-side state. CSRF attacks if not careful (use SameSite cookies and CSRF tokens).
Use when: traditional web apps where browser is the primary client.
What Goes Where in Headers
Always use the Authorization header. Standard format: Authorization: <scheme> <credentials>. Common schemes: Bearer, Basic, custom (e.g., AWS4-HMAC-SHA256).
Avoid: putting tokens in URL query parameters. They get logged everywhere (server logs, browser history, referer headers).
Layering Defenses
Real systems combine patterns:
API key for identifying the caller + rate limiting per key.
JWT for individual user identity within a request.
mTLS at the network layer for service-to-service.
IP allowlisting for additional restriction.
MFA at the user-facing edge for high-stakes operations.
Common Mistakes
Storing API keys in code or git. Use environment variables or a secrets manager. Rotate immediately if leaked.
Long-lived JWTs without refresh tokens. Short-lived access + long-lived refresh is the right pattern.
Logging full Authorization headers. Redact in logs.
Not rate-limiting per credential. Without it, a leaked key can drain your service.
Returning detailed error messages. "User not found" vs "wrong password" leaks info to attackers. Use generic messages.
The One Thing to Remember
The right authentication scheme depends on who's calling and what's at stake. API keys are fine for most server-to-server APIs. JWTs work well for user sessions across distributed services. mTLS for high-trust internal systems. HMAC signing for tamper-proof requests. The pattern matters less than the discipline: rotate secrets, monitor usage, rate-limit, log access, and assume credentials will eventually leak.