API Keys vs Tokens — Deep Comparison of Authentication Strategies
In this tutorial, you will learn about API Keys vs Tokens. We cover key concepts, practical examples, and best practices to help you master this topic.
API keys and tokens serve different authentication purposes: API keys are static identifiers for machine clients, while tokens are dynamic, scoped, and time-limited credentials issued after user authentication.
What You'll Learn
The fundamental differences between API keys and tokens, when to use each, how to implement both, rotation and revocation patterns, and hybrid approaches.
Why It Matters
Choosing the wrong credential type impacts security, developer experience, and operational overhead. Using tokens where API keys suffice adds unnecessary complexity, while using API keys where tokens are needed creates security gaps.
Real-World Use
Stripe uses API keys for server-to-server communication and publishable keys for client-side usage. Auth0 issues JWTs for user sessions. Durga Antivirus Pro uses API keys for internal microservice communication and JWTs for partner portal access.
flowchart TD
A["Credential Type?"] --> B{"Machine-to-Machine?"}
B -->|"Yes"| C["API Key"]
B -->|"No"| D{"User Session?"}
D -->|"Yes"| E["JWT Token"]
D -->|"No"| F{"Third-Party?"}
F -->|"Yes"| G["OAuth2 Token"]
F -->|"No"| H["API Key"]
C --> I["Static, long-lived\nScoped per service"]
E --> J["Dynamic, short-lived\nClaims-based"]
G --> K["Delegated, scoped\nRefresh rotation"]
style C fill:#dbeafe,stroke:#2563eb
style E fill:#fef3c7,stroke:#d97706
style G fill:#dcfce7,stroke:#16a34a
Code Example: API Key Authentication Middleware
from flask import Flask, request, jsonify, g
import hashlib, secrets, os
app = Flask(__name__)
# Hashed API keys with metadata
api_keys = {
hashlib.sha256("sk-durga-scan-v1".encode()).hexdigest(): {
"service": "scanner",
"scopes": ["scan:read", "scan:write"],
"active": True,
"created": "2026-01-15"
}
}
@app.before_request
def authenticate_api_key():
# Skip non-API paths
if not request.path.startswith("/api/"):
return
api_key = request.headers.get("X-API-Key")
if not api_key:
return jsonify({"error": "API key required"}), 401
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
key_data = api_keys.get(key_hash)
if not key_data or not key_data.get("active"):
return jsonify({"error": "Invalid or revoked API key"}), 401
g.service = key_data["service"]
g.scopes = key_data["scopes"]
@app.route("/api/v1/scans")
def list_scans():
if "scan:read" not in g.scopes:
return jsonify({"error": "Insufficient scope"}), 403
return jsonify({"scans": [], "service": g.service})
Code Example: JWT Token Authentication Middleware
import jwt
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, g
app = Flask(__name__)
SECRET = os.environ.get("JWT_SECRET", "dev-secret")
@app.before_request
def authenticate_token():
if not request.path.startswith("/api/"):
return
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "Bearer token required"}), 401
try:
token = auth[7:]
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
g.user = payload.get("sub")
g.roles = payload.get("roles", [])
g.session_id = payload.get("jti")
except jwt.ExpiredSignatureError:
return jsonify({"error": "Token expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid token"}), 401
@app.route("/api/v1/profile")
def profile():
return jsonify({
"user": g.user,
"roles": g.roles,
"session": g.session_id
})
Code Example: Hybrid Approach — API Key + Token Exchange
@app.route("/api/v1/exchange", methods=["POST"])
def exchange_api_key():
"""Exchange an API key for a short-lived JWT."""
api_key = request.headers.get("X-API-Key")
if not api_key:
return jsonify({"error": "API key required"}), 401
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
key_data = api_keys.get(key_hash)
if not key_data or not key_data.get("active"):
return jsonify({"error": "Invalid API key"}), 401
# Issue short-lived JWT
token = jwt.encode({
"sub": key_data["service"],
"scopes": key_data["scopes"],
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(minutes=15),
"jti": secrets.token_hex(16)
}, SECRET, algorithm="HS256")
return jsonify({
"token": token,
"expires_in": 900,
"token_type": "Bearer"
})
Common Mistakes
1. Using API Keys for User Authentication
API keys are for machines. They cannot be revoked per-user and do not support granular session management. Use tokens for user sessions.
2. Embedding API Keys in Client-Side Code
API keys in mobile apps or SPAs are exposed to users. Use a backend proxy or OAuth2 with PKCE instead.
3. Not Hashing API Keys in Storage
Store SHA-256 hashes of API keys, not the raw keys. If the database is breached, hashed keys cannot be used directly.
4. Over-Scoping Tokens
Issuing tokens with wildcard scopes (admin, *) defeats the purpose of scope-based access. Grant the minimum scope needed.
5. No Key Rotation Policy
API keys without rotation accumulate risk. Implement key rotation with overlapping validity periods so services can switch without downtime.
Practice Questions
- What is the primary use case for API keys vs tokens?
- Why should API keys be hashed in the database?
- How does token expiry improve security over static keys?
- What is a key rotation Strategy that avoids downtime?
- When would you use a hybrid API-key-to-token exchange?
Answers:
- API keys identify machines and services. Tokens represent authenticated user sessions or delegated authorization.
- If the database is compromised, hashed keys cannot be used to authenticate. The attacker would need to brute-force the hash.
- A compromised token is only valid until expiry (minutes or hours). A compromised API key is valid until explicitly revoked.
- Issue a new key alongside the old one, mark the new key as primary, then remove the old key after a transition period.
- When a service receives an API key but needs to make multiple downstream calls — the exchanged JWT carries scoped claims for each call.
Challenge: Build a credential management system that supports both API keys (for services) and JWT tokens (for users), with a key-to-token exchange endpoint.
FAQ
Mini Project
Build a system with two auth mechanisms: API keys for internal Microservices (hashed storage, scoped) and JWT tokens for user sessions (short-lived, refresh rotation). Include a key management dashboard and token refresh endpoint.
What's Next
Now learn about Token Storage Strategies for securely storing credentials on the client side.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro