DEV Community

Sir Max
Sir Max

Posted on

API Authentication in 2026: API Keys vs JWT vs OAuth 2.0 — When to Use What

API Authentication in 2026: API Keys vs JWT vs OAuth 2.0 — When to Use What

I remember the first time I shipped an API without authentication. "It's just an internal tool," I told myself. Two weeks later, I found our database being scraped by a competitor's bot at 3 AM. That was a $1,200 AWS bill and a very awkward conversation with my team lead.

Authentication isn't glamorous. Nobody wakes up excited to implement auth. But get it wrong, and it's the kind of mistake that wakes you up at 3 AM with a PagerDuty alert.

After building and securing dozens of APIs over the years, here's my practical guide to the three most common auth patterns — and when to use each one.


1. API Keys — The Simple Workhorse

API Keys are the "hello world" of API auth. A long random string that the client sends with every request. No JWT parsing, no token exchange, no refresh flows.

# Server-side API key validation (FastAPI)
from fastapi import FastAPI, HTTPException, Header

app = FastAPI()

# In production, load these from env vars or a database
VALID_KEYS = {"sk-demo-key-1", "sk-demo-key-2"}

@app.get("/data")
def get_data(x_api_key: str = Header(...)):
    if x_api_key not in VALID_KEYS:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return {"temperature": 22.5, "humidity": 65}
Enter fullscreen mode Exit fullscreen mode

The client just adds one header:

curl -H "X-API-Key: sk-demo-key-1" https://api.example.com/data
Enter fullscreen mode Exit fullscreen mode

When to use API Keys

  • Public APIs where users need simple access — think weather APIs, stock data feeds
  • Server-to-server communication where you control both ends
  • Prototyping — get something working fast, upgrade later

When NOT to use

  • User-facing apps where different users need different permissions
  • When you need to revoke access granularly (it's all-or-nothing with a single key)
  • When handling sensitive user data that requires audit trails

Real talk: I still use API Keys for most internal microservices. They're fast to implement, easy to debug, and honestly — for 80% of use cases — they're good enough. Don't over-engineer.


2. JWT (JSON Web Tokens) — Stateless & Scalable

JWTs are signed tokens that carry claims (user info, permissions, expiry) right inside the token. No database lookup needed to validate — the signature proves authenticity.

import jwt
from datetime import datetime, timedelta

SECRET = "your-256-bit-secret"  # Use env vars in production!

def create_token(user_id: str, role: str) -> str:
    """Generate a JWT that expires in 1 hour."""
    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(token: str) -> dict:
    """Validate and decode a JWT. Raises on expiry or tampering."""
    try:
        return jwt.decode(token, SECRET, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise Exception("Token expired — re-authenticate")
    except jwt.InvalidTokenError:
        raise Exception("Invalid token — possible tampering")
Enter fullscreen mode Exit fullscreen mode

Client sends it in the standard header:

curl -H "Authorization: Bearer eyJhbGciOi... " https://api.example.com/profile
Enter fullscreen mode Exit fullscreen mode

When to use JWT

  • Microservices — each service validates independently, no central auth bottleneck
  • Mobile apps / SPAs — store the token client-side, include it in the Authorization header
  • High-traffic APIs — no database hit per request for auth validation

When NOT to use

  • When you need to revoke tokens immediately (JWTs are valid until they expire)
  • When payload size matters — large JWTs add up across thousands of requests
  • When you need server-side session management (use opaque tokens + Redis instead)

The JWT trap I fell into: I once stuffed the user's entire profile inside the JWT — name, email, preferences, subscription tier. The token was 2KB. Every API call was transmitting 2KB of overhead. Don't do that. Keep claims minimal: user ID, role, expiry. That's it.


3. OAuth 2.0 — The Heavy Artillery

OAuth 2.0 is a delegation protocol. It lets a user grant limited access to their resources without sharing their password. Think "Sign in with Google" — you're not giving the app your Google password; you're granting it a scoped access token.

# Simplified Authorization Code flow (FastAPI + httpx)
import httpx
from fastapi import FastAPI
from urllib.parse import urlencode

app = FastAPI()
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
REDIRECT_URI = "https://yourapp.com/callback"


@app.get("/login")
async def login():
    params = {
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "response_type": "code",
        "scope": "read:user repo",
        "state": "random_state_string",
    }
    auth_url = (
        f"https://github.com/login/oauth/authorize?"
        f"{urlencode(params)}"
    )
    return {"redirect_to": auth_url}


@app.get("/callback")
async def callback(code: str, state: str):
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://github.com/login/oauth/access_token",
            json={
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
                "code": code,
                "redirect_uri": REDIRECT_URI,
            },
            headers={"Accept": "application/json"},
        )
        return resp.json()
Enter fullscreen mode Exit fullscreen mode

When to use OAuth 2.0

  • Third-party integrations — "Sign in with Google/GitHub"
  • Multi-service ecosystems where users grant different permissions to different apps
  • Enterprise SSO scenarios

When NOT to use

  • Simple internal APIs — massive overkill
  • When you only have one client — API Keys or JWT are far simpler
  • Small teams that need to ship fast — OAuth 2.0 is a time sink

Honest opinion: OAuth 2.0 is the most over-implemented spec I've seen. I've watched teams spend three sprints building an OAuth server for an API that had 12 users. If you don't have a clear multi-client, multi-permission use case, start with JWT and migrate later.


Decision Matrix

Scenario Go With Why
Public weather/stock API API Keys Simple, rate-limit per key
Internal microservices API Keys Fast, zero overhead
Mobile app backend JWT Stateless, scales horizontally
SaaS with user roles JWT + refresh tokens Granular permissions
"Sign in with Google" OAuth 2.0 Industry standard for delegation
Enterprise B2B integration OAuth 2.0 Clients expect it
Side project / MVP API Keys then upgrade Ship first, secure later

What I Actually Do

After years of overthinking this, here's my default approach:

  1. Start with API Keys for any new internal service
  2. Add JWT when user authentication becomes necessary
  3. Consider OAuth 2.0 only when a client explicitly asks for it, or when building a platform that third-party apps integrate with

The best auth system is the one you actually implement correctly — not the theoretically perfect one sitting in your architecture diagram that took six months and never shipped.


What auth pattern are you using for your APIs? I'd love to hear about setups I haven't tried — drop a comment below.

Top comments (0)