How to Build API Key Authentication
API key auth gates an endpoint behind a secret key. The rules that make it secure: generate a strong random key, store only its hash, show the raw value once, and verify the X-API-Key header on each request. Here's how.
How API key auth works
Each client gets a secret key. They send it on every request (an X-API-Key header is the
convention), and your middleware checks it before the handler runs. The security is all in
how you generate and store the key.
Generate a strong key, store only its hash
Treat keys like passwords - never store the raw value. Generate with a CSPRNG and persist the hash:
import secrets, hashlib
def new_key():
raw = "sk_" + secrets.token_urlsafe(32) # strong, unguessable
key_hash = hashlib.sha256(raw.encode()).hexdigest()
db.execute("INSERT INTO api_keys (key_hash, created_at) VALUES (?, ?)",
(key_hash, time.time()))
return raw # return the RAW key ONCE - it's never recoverable after this
Show the raw key to the user exactly once on creation. After that you only ever have the hash, so a database leak doesn't expose usable keys.
Verify on every request
Hash the incoming key and look up the hash - constant work, no raw keys in the DB:
from functools import wraps
from flask import request, jsonify
def require_api_key(f):
@wraps(f)
def wrapper(*args, **kwargs):
raw = request.headers.get("X-API-Key", "")
key_hash = hashlib.sha256(raw.encode()).hexdigest()
row = db.execute("SELECT id FROM api_keys WHERE key_hash = ? AND revoked = 0",
(key_hash,)).fetchone()
if not row:
return jsonify({"error": "invalid api key"}), 401
return f(*args, **kwargs)
return wrapper
@app.get("/api/data")
@require_api_key
def data(): ...
The rules that make it secure
- Hash storage (SHA-256 is fine for high-entropy random keys; you don't need bcrypt when the key is already 256 bits of randomness).
- Show the raw key once - never log it, never store it, never email it.
- Support revocation - a
revokedflag (or delete) so a leaked key can be killed. - Prefix keys (
sk_...) so they're recognizable and scannable in secret-detection. - Rate-limit per key to cap abuse from any single client.
- 401 for missing/invalid, and always require HTTPS so the key isn't sent in clear.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Generating keys with a CSPRNG and storing only the hash
- Verifying the X-API-Key header in middleware
- Adding revocation + per-key rate limiting
FAQ
How should I store API keys?
Store only a hash of the key, never the raw value - generate the key with a CSPRNG, hash it (SHA-256 is enough for a high-entropy random key), and show the raw key to the user only once at creation.
Where should the API key go in a request?
Conventionally an X-API-Key header (or Authorization: Bearer <key>). Verify it in middleware before the handler runs and return 401 if it's missing or invalid. Always over HTTPS.
Do I need bcrypt for API keys like for passwords?
No - bcrypt's slowness defends weak human passwords against brute force. A randomly generated 256-bit key has too much entropy to brute force, so a fast SHA-256 hash is appropriate and lets you look it up efficiently.
What is the difference between API key and basic authentication?
Basic auth sends a username and password (base64-encoded) on every request; an API key is a single opaque token sent in a header like X-API-Key. API keys are easier to issue, scope, and revoke per client without exposing user credentials.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →