API Key vs OAuth vs JWT
The Three Pillars of API Authentication
Every API needs to answer a fundamental question: who is making this request? The three dominant approaches — API keys, OAuth 2.0, and JSON Web Tokens — each answer that question differently, with distinct tradeoffs in complexity, security, and flexibility.
API Keys
An API key is a unique string assigned to a client application. The client includes it in every request, typically as a header or query parameter.
GET /api/v1/weather?city=london
X-API-Key: sk_live_4eC39HqLyjWDarjtT1zdp7dc
How API Keys Work
The server generates a random, high-entropy string and associates it with a client account. On each request, the server looks up the key in a database, identifies the client, and applies the appropriate rate limits and permissions.
{
"key": "sk_live_4eC39HqLyjWDarjtT1zdp7dc",
"client": "acme-corp",
"created_at": "2024-03-15T10:00:00Z",
"permissions": ["read:weather", "read:forecasts"],
"rate_limit": 1000
}
When to Use API Keys
API keys work best for server-to-server communication where a single application authenticates itself. Stripe uses API keys extensively: your backend server calls Stripe's API with a secret key that never leaves your server.
Key characteristics:
- Simple to implement and use
- Per-client identification and rate limiting
- No user context — the key represents an application, not a person
- Must be kept secret — never embed in client-side code
Rotation Strategy
API keys should be rotated regularly. The standard approach is to support two active keys simultaneously so you can deploy the new key before revoking the old one.
{
"primary_key": "sk_live_new_key_here",
"secondary_key": "sk_live_old_key_here",
"rotation_deadline": "2024-04-15T00:00:00Z"
}
Stripe, Twilio, and AWS all support this dual-key rotation pattern.
API Key Security
Never store API keys in plaintext in your database. Hash them with a strong algorithm (SHA-256 or bcrypt). When the client sends a key, hash the input and compare against the stored hash. If your database is compromised, attackers get hashes, not usable keys.
Prefix keys with an identifier that indicates the key type and environment:
sk_live_4eC39HqLyjWDarjtT1zdp7dc (secret, live)
pk_live_abc123 (publishable, live)
sk_test_xyz789 (secret, test)
Stripe popularized this pattern. The prefix helps developers immediately identify whether a key is for production or testing, and whether it should be kept secret.
OAuth 2.0
OAuth 2.0 is an authorization framework that lets users grant third-party applications limited access to their resources without sharing credentials.
Authorization Code Flow (Web Applications)
This is the standard flow for web applications where a user grants access to a third-party app.
1. User clicks "Connect with GitHub"
2. App redirects to: https://github.com/login/oauth/authorize?
client_id=abc123&
redirect_uri=https://myapp.com/callback&
scope=repo,user:email&
state=xyz789
3. User authorizes on GitHub
4. GitHub redirects to: https://myapp.com/callback?code=AUTH_CODE&state=xyz789
5. App exchanges code for token (server-side):
POST https://github.com/login/oauth/access_token
{ client_id, client_secret, code, redirect_uri }
6. GitHub returns access token
The state parameter prevents CSRF attacks. The authorization code is short-lived and exchanged server-side, so the access token never passes through the browser.
Client Credentials Flow (Service-to-Service)
When no user is involved and one service authenticates to another, the client credentials flow is simpler.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=service_abc&
client_secret=secret_xyz&
scope=read:analytics
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:analytics"
}
When to Use OAuth 2.0
OAuth is the right choice when users delegate access to third-party applications. Google, GitHub, Slack, and virtually every major platform uses OAuth for third-party integrations. The user remains in control: they can see what permissions they granted and revoke access at any time.
JSON Web Tokens (JWT)
A JWT is a self-contained token that carries claims about the subject. Unlike API keys, a JWT does not require a database lookup to validate — the server verifies the cryptographic signature and reads the claims directly.
JWT Structure
A JWT has three Base64URL-encoded parts separated by dots: header, payload, and signature.
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInNjb3BlIjoicmVhZDp1c2VycyJ9.signature
Decoded payload:
{
"sub": "user_123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1711036800,
"iat": 1711033200,
"scope": "read:users write:orders"
}
Signing Algorithms
JWTs support symmetric and asymmetric signing:
- HS256 (HMAC-SHA256): Symmetric. The same secret signs and verifies. Simple, but the secret must be shared with every service that validates tokens.
- RS256 (RSA-SHA256): Asymmetric. A private key signs; a public key verifies. The auth server keeps the private key. Any service can verify with the public key, which can be distributed freely.
- ES256 (ECDSA-SHA256): Asymmetric like RS256, but with smaller keys and faster operations.
For microservice architectures, use RS256 or ES256. Each service gets the public key (often via a JWKS endpoint) and validates tokens independently. No shared secrets to manage.
{
"keys": [
{
"kty": "RSA",
"kid": "key_2024_03",
"use": "sig",
"n": "0vx7agoebGcQ...",
"e": "AQAB"
}
]
}
When to Use JWT
JWTs shine in stateless authentication. The token carries all the information the server needs, eliminating database lookups on every request. Auth0 and Firebase Authentication issue JWTs as access tokens. Microservice architectures benefit because each service can validate the token independently without calling back to an auth service.
The Token Lifecycle
Every token-based system follows the same lifecycle:
Issue: The auth server verifies credentials and creates a token. For JWTs, this means signing the payload with a private key.
Validate: On each request, the server checks the signature, expiration (exp), audience (aud), and issuer (iss).
Refresh: Access tokens are short-lived (15 minutes to 1 hour). A refresh token, stored securely, obtains new access tokens without re-authentication.
{
"access_token": "eyJhbGciOi...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"token_type": "Bearer",
"expires_in": 900
}
Revoke: For API keys and OAuth tokens, delete from the database. For JWTs, you need a revocation list or short expiration times since JWTs cannot be invalidated individually without server-side state.
Choosing the Right Approach
| Criteria | API Key | OAuth 2.0 | JWT |
|---|---|---|---|
| Complexity | Low | High | Medium |
| User delegation | No | Yes | No (used as token format) |
| Stateless validation | No | No | Yes |
| Revocation | Immediate | Immediate | Requires revocation list |
| Best for | Server-to-server | User-delegated access | Stateless auth between services |
In practice, these approaches are often combined. OAuth 2.0 issues access tokens that are formatted as JWTs. An API key authenticates a service that then uses OAuth to act on behalf of users.
Common Pitfalls
Embedding API keys in client-side code. API keys in JavaScript, mobile apps, or public repositories are immediately compromised. GitHub scans every public commit for known key patterns and alerts providers.
Using JWTs with long expiration times. A JWT that expires in 30 days cannot be revoked for 30 days unless you maintain a server-side blocklist, which defeats the purpose of stateless tokens. Keep access token lifetimes under an hour.
Skipping the state parameter in OAuth. Without state, an attacker can forge the callback URL and link their own account to the victim's third-party account. Always generate a random state value and verify it on callback.
Storing refresh tokens insecurely. Refresh tokens are long-lived credentials. Store them encrypted, in HTTP-only cookies or secure server-side storage. Never in localStorage.
Not rotating API keys. Keys that never rotate accumulate risk. Former employees, leaked logs, and compromised systems all create exposure. Rotate keys on a schedule and after any suspected breach.
Key Takeaways
- API keys are simple and effective for server-to-server authentication; rotate them regularly and never expose them in client-side code.
- OAuth 2.0 is the standard for user-delegated access; use the authorization code flow for web apps and client credentials for service-to-service.
- JWTs provide stateless authentication by embedding claims directly in the token; keep expiration times short because revocation is difficult.
- These approaches are complementary, not competing — OAuth commonly issues JWTs as access tokens.
- The token lifecycle (issue, validate, refresh, revoke) applies to all token-based systems; design for all four stages from the start.