API Authentication Guide — JWT, API Keys, and OAuth 2.0

Learn API authentication methods: API keys, JWT tokens, OAuth 2.0, and session-based auth. When to use each and how to implement them securely.

Choosing an Authentication Method

There is no single best authentication method — the right choice depends on who is calling your API and from where.

  • API Keys — for server-to-server calls, developer access, and machine clients
  • JWT — for stateless user authentication in SPAs and mobile apps
  • OAuth 2.0 — for third-party access delegation ("Login with Google")
  • Session cookies — for traditional web apps where the server maintains state

API Keys

API keys are long random strings shared as a secret between the client and server. Simple, stateless, and easy to implement.

# Generate a secure API key
import secrets
api_key = secrets.token_urlsafe(32)  # 43 chars, URL-safe base64

# Store hashed (never store plaintext)
import hashlib
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
# Store key_hash in database, show raw key once to user

# Verify on each request
def verify_api_key(provided_key: str, stored_hash: str) -> bool:
    provided_hash = hashlib.sha256(provided_key.encode()).hexdigest()
    return secrets.compare_digest(provided_hash, stored_hash)

Best practices: prefix keys for easy identification (sk_live_...), allow multiple keys per account, log key usage, implement per-key rate limits.

JWT (JSON Web Tokens)

JWTs are self-contained tokens with a header, payload, and signature. The server can verify them without a database lookup.

import jwt
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key"

# Create a token
def create_access_token(user_id: int) -> str:
    payload = {
        "sub": str(user_id),
        "iat": datetime.utcnow(),
        "exp": datetime.utcnow() + timedelta(hours=1),
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")

# Verify and decode
def verify_token(token: str) -> dict:
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise Exception("Token expired")
    except jwt.InvalidTokenError:
        raise Exception("Invalid token")

OAuth 2.0 Authorization Code Flow

OAuth 2.0 lets users grant your app access to their data on another service without sharing their password. The Authorization Code flow is the most secure variant:

  1. Redirect user to authorization server with client_id, redirect_uri, scope, and a random state value
  2. User logs in and approves access — server redirects back with an authorization code
  3. Your server exchanges the code for an access_token (server-to-server, includes client_secret)
  4. Use the access token to call the resource server on the user's behalf

Always use PKCE (Proof Key for Code Exchange) for public clients (SPAs, mobile apps) to prevent authorization code interception attacks.

Frequently Asked Questions

Should I store JWTs in localStorage or cookies?

HttpOnly cookies are more secure — they're not accessible to JavaScript, preventing XSS attacks from stealing tokens. localStorage is vulnerable to XSS. For cookies, also set Secure (HTTPS only) and SameSite=Strict or Lax to prevent CSRF.

What is the difference between access tokens and refresh tokens?

Access tokens are short-lived (15 minutes to 1 hour) and sent with every API request. Refresh tokens are long-lived (days to weeks), stored securely, and used only to get new access tokens. This limits the damage window if an access token is compromised.

How do I revoke a JWT?

JWTs are stateless — you can't revoke them without a server-side blocklist. Use short expiry times (15 minutes), maintain a token blocklist (in Redis) for logout, or switch to opaque tokens with a database lookup for use cases where instant revocation is critical.