In this tutorial, you'll learn about API Auth. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
API authentication validates client identity using API keys, JWT tokens, or OAuth2 Client Credentials — each suited to different trust levels and security requirements.
What You'll Learn
You will learn how to implement three API authentication methods — API keys, JWT bearer tokens, and OAuth2 Client Credentials flow — and when to choose each.
Why Authentication Matters
An unauthenticated API is a public endpoint anyone can call. Authentication ensures only authorized clients access your data. DodaTech's Durga Antivirus Pro partner API authenticates 5,000+ integrations — a compromised key could expose threat intelligence to competitors.
Method 1: API Keys
Simple, static tokens passed in headers or query parameters.
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
API_KEYS = {
"dodatech_partner_key_abc123": {"client": "PartnerCorp", "tier": "premium"},
"dodatech_partner_key_def456": {"client": "StartupInc", "tier": "basic"},
}
def authenticate_api_key():
api_key = request.headers.get("X-API-Key")
if not api_key:
api_key = request.args.get("api_key")
if not api_key or api_key not in API_KEYS:
abort(401, description="Invalid or missing API key")
return API_KEYS[api_key]
@app.route("/v2/threats")
def list_threats():
client = authenticate_api_key()
return jsonify({
"client": client["client"],
"tier": client["tier"],
"threats": fetch_threats(tier=client["tier"]),
})
Expected output:
curl -H "X-API-Key: dodatech_partner_key_abc123" \
http://localhost:5000/v2/threats
# {"client": "PartnerCorp", "tier": "premium", "threats": [...]}
curl http://localhost:5000/v2/threats
# HTTP/1.1 401 UNAUTHORIZED
# {"error": "Invalid or missing API key"}
Method 2: JWT Bearer Tokens
Stateless tokens with encoded claims and expiration.
import jwt
from datetime import datetime, timedelta, timezone
SECRET_KEY = "dodatech-jwt-secret-key-change-in-production"
def create_jwt(client_id, tier):
payload = {
"sub": client_id,
"tier": tier,
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_jwt():
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
if not token:
abort(401, description="Missing Authorization header")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
abort(401, description="Token expired")
except jwt.InvalidTokenError:
abort(401, description="Invalid token")
@app.route("/v2/auth/login", methods=["POST"])
def login():
data = request.get_json()
if data.get("api_key") in API_KEYS:
client = API_KEYS[data["api_key"]]
token = create_jwt(client["client"], client["tier"])
return jsonify({"access_token": token, "token_type": "Bearer"})
abort(401, description="Invalid credentials")
@app.route("/v2/threats/scan")
def scan_threat():
payload = verify_jwt()
return jsonify({
"client": payload["sub"],
"scan_result": simulate_scan(),
})
Expected output:
curl -X POST http://localhost:5000/v2/auth/login \
-H "Content-Type: application/json" \
-d '{"api_key": "dodatech_partner_key_abc123"}'
# {"access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "Bearer"}
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
http://localhost:5000/v2/threats/scan
# {"client": "PartnerCorp", "scan_result": "clean"}
Method 3: OAuth2 Client Credentials
import httpx
# OAuth2 token endpoint (simulated auth server)
AUTH_SERVER = "https://auth.dodatech.com/oauth/token"
CLIENT_ID = "dodatech-api"
CLIENT_SECRET = "client-secret-change-me"
def get_client_credentials_token():
response = httpx.post(
AUTH_SERVER,
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "threats:read threats:write",
},
)
response.raise_for_status()
return response.json()
# Token introspection
def introspect_token(token):
response = httpx.post(
f"{AUTH_SERVER}/introspect",
data={"token": token},
auth=(CLIENT_ID, CLIENT_SECRET),
)
return response.json()
@app.route("/v2/oauth/threats")
def oauth_threats():
auth_header = request.headers.get("Authorization", "")
token = auth_header.removeprefix("Bearer ")
introspection = introspect_token(token)
if not introspection.get("active"):
abort(401, description="Token is invalid or expired")
return jsonify({
"client_id": introspection.get("client_id"),
"scope": introspection.get("scope"),
"threats": fetch_threats(),
})
Expected output:
# Get token from auth server
curl -X POST https://auth.dodatech.com/oauth/token \
-u "dodatech-api:client-secret-change-me" \
-d "grant_type=client_credentials&scope=threats:read"
# {"access_token": "eyJraWQiOiJ...", "expires_in": 3600, "scope": "threats:read"}
# Use token to access API
curl -H "Authorization: Bearer eyJraWQiOiJ..." \
http://localhost:5000/v2/oauth/threats
# {"client_id": "dodatech-api", "scope": "threats:read", "threats": [...]}
flowchart TD
A["Choose Auth Method"] --> B{"Who calls\nthe API?"}
B -->|"Server-to-Server"| C["OAuth2 Client Credentials\nBest for machine-to-machine"]
B -->|"First-party clients"| D["API Keys\nSimple, static tokens"]
B -->|"Third-party apps"| E["OAuth2 Authorization Code\nDelegated user auth"]
B -->|"SPA / Mobile"| F["JWT Bearer Tokens\nStateless sessions"]
C --> G["+ Scoped permissions"]
C --> H["- Requires auth server"]
D --> I["+ Simple to implement"]
D --> J["- Hard to revoke per-client"]
E --> K["+ User consent flow"]
F --> L["+ No server-side state"]
style A fill:#dbeafe,stroke:#2563eb
style C fill:#bbf7d0,stroke:#16a34a
style D fill:#fef3c7,stroke:#d97706
style F fill:#e0e7ff,stroke:#4f46e5
Common Errors
1. Storing Secrets in Code
Hardcoded API keys and JWT secrets in source code are exposed in Git history. Use environment variables or a secrets manager.
2. JWT Without Expiration
Tokens without exp never expire. If a token leaks, it is valid forever. Always set short expiration (15-60 minutes) and use refresh tokens for longer sessions.
3. Not Validating Token Scope
A token issued for threats:read should not access threats:write. Always validate the scope claim against the requested operation.
4. API Key Leakage in URLs
Passing API keys as query parameters leaks them in server logs, referrer headers, and browser history. Use the Authorization header instead.
5. Missing Rate Limits on Auth Endpoints
The login endpoint is the most attacked endpoint. Rate limit it aggressively (5 attempts per minute per IP) and implement account lockout.
Practice Questions
1. What is the difference between authentication and authorization?
Authentication verifies identity ("who you are"). Authorization determines permissions ("what you can do").
2. When would you use OAuth2 Client Credentials vs JWT?
Client Credentials when you need delegated authorization with scope and an auth server. JWT when you want self-contained stateless tokens without an external auth server.
3. How do you revoke a JWT before it expires?
Maintain a denylist of revoked JWT IDs (jti) on the server, or use short-lived tokens with refresh tokens that can be revoked individually.
4. Challenge: Implement token refresh for the JWT auth system.
REFRESH_TOKENS = {}
def create_refresh_token(client_id):
token = secrets.token_urlsafe(32)
REFRESH_TOKENS[token] = client_id
return token
@app.route("/v2/auth/refresh", methods=["POST"])
def refresh():
refresh_token = request.get_JSON().get("refresh_token")
client_id = REFRESH_TOKENS.pop(refresh_token, None)
if not client_id:
abort(401, description="Invalid refresh token")
new_token = create_JWT(client_id, "premium")
new_refresh = create_refresh_token(client_id)
return jsonify({"access_token": new_token, "refresh_token": new_refresh})
Mini Project: Auth Middleware
Build an authentication middleware for DodaTech's threat API that supports API key auth (legacy partners) and JWT auth (new partners). Include middleware that checks auth on every endpoint and logs which client accessed which endpoint.
Related Tutorials
JWT — OAuth 2.0 — RESTful API Design
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro