Every API you build needs authentication. But which method should you use?
I've implemented all three in production systems. Here's when to use each — with working Python code.
Quick Decision Tree
Is it a public API (no user data)? → API Key
Does it need user-specific data? → OAuth2
Is it internal/microservices? → JWT
Is it server-to-server? → API Key or JWT
1. API Keys — The Simple Choice
Use when: Public APIs, rate limiting, usage tracking.
from fastapi import FastAPI, Header, HTTPException
import secrets
app = FastAPI()
# In production: store in database
VALID_KEYS = {
secrets.token_hex(32): {'user': 'client_1', 'tier': 'free'},
}
@app.get('/api/data')
async def get_data(x_api_key: str = Header(...)):
if x_api_key not in VALID_KEYS:
raise HTTPException(401, 'Invalid API key')
client = VALID_KEYS[x_api_key]
return {'data': 'here', 'client': client['user']}
Pros: Dead simple. Works everywhere. Easy to rotate.
Cons: No user identity. Keys can leak in logs/URLs. No expiration by default.
Security tip: Never put API keys in URL query params — they end up in server logs and browser history. Use headers.
2. JWT (JSON Web Tokens) — The Stateless Choice
Use when: Microservices, SPAs, mobile apps where you need user identity without hitting a database on every request.
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer
import jwt
from datetime import datetime, timedelta
app = FastAPI()
security = HTTPBearer()
SECRET = 'your-256-bit-secret' # In production: env var
def create_token(user_id: str, role: str = 'user') -> str:
payload = {
'sub': user_id,
'role': role,
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow(),
}
return jwt.encode(payload, SECRET, algorithm='HS256')
def verify_token(credentials = Depends(security)):
try:
payload = jwt.decode(credentials.credentials, SECRET, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(401, 'Token expired')
except jwt.InvalidTokenError:
raise HTTPException(401, 'Invalid token')
@app.post('/auth/login')
async def login(username: str, password: str):
# Verify credentials (simplified)
if username == 'admin' and password == 'secret':
token = create_token(username, role='admin')
return {'access_token': token, 'token_type': 'bearer'}
raise HTTPException(401, 'Invalid credentials')
@app.get('/api/protected')
async def protected(user = Depends(verify_token)):
return {'message': f"Hello {user['sub']}", 'role': user['role']}
Pros: Stateless (no session store). Contains user info. Cross-service compatible.
Cons: Can't revoke individual tokens (use short expiry + refresh tokens). Token size can be large.
Security tip: NEVER store JWTs in localStorage — use httpOnly cookies to prevent XSS theft.
3. OAuth2 — The Enterprise Choice
Use when: Third-party access, "Login with Google/GitHub", delegated permissions.
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
GITHUB_CLIENT_ID = 'your_client_id'
GITHUB_CLIENT_SECRET = 'your_client_secret'
@app.get('/auth/github')
async def github_login():
return {
'url': f'https://github.com/login/oauth/authorize?client_id={GITHUB_CLIENT_ID}&scope=user:email'
}
@app.get('/auth/github/callback')
async def github_callback(code: str):
# Exchange code for access token
async with httpx.AsyncClient() as client:
token_response = await client.post(
'https://github.com/login/oauth/access_token',
json={
'client_id': GITHUB_CLIENT_ID,
'client_secret': GITHUB_CLIENT_SECRET,
'code': code,
},
headers={'Accept': 'application/json'},
)
access_token = token_response.json()['access_token']
# Get user info
user_response = await client.get(
'https://api.github.com/user',
headers={'Authorization': f'Bearer {access_token}'},
)
user = user_response.json()
# Create session/JWT for your app
app_token = create_token(user['login'])
return {'user': user['login'], 'token': app_token}
Pros: Industry standard. Granular permissions. Users don't share passwords.
Cons: Complex flow. Requires redirect handling. Token management is tricky.
Comparison Table
| Feature | API Key | JWT | OAuth2 |
|---|---|---|---|
| Complexity | Low | Medium | High |
| User identity | No | Yes | Yes |
| Expiration | Manual | Built-in | Built-in |
| Revocation | Easy | Hard | Medium |
| Best for | Public APIs | Internal/SPAs | Third-party |
| Stateless | Yes | Yes | No (needs auth server) |
My Recommendations for 2026
Start with API keys. If you're building an API and don't need user identity, API keys are fine. Add JWT later if needed.
Use short-lived JWTs + refresh tokens. Access token: 15 minutes. Refresh token: 7 days. This limits damage from leaked tokens.
Never implement OAuth2 yourself. Use a library (FastAPI has built-in support) or a service (Auth0, Clerk, Supabase Auth).
Always HTTPS. All three methods send secrets in headers. Without TLS, anyone on the network can steal them.
Which auth method do you prefer for your APIs? Have you tried any of the newer approaches (like Passkeys or FIDO2)? 👇
More from me: 10 Dev Tools I Use Daily | 77 Scrapers on a Schedule | 150+ Free APIs
Top comments (0)