27.7. API Keys
Table of Contents
Why API Keys Exist
API keys are simple strings that identify who is calling an API. They are usually used to:
- Track usage per client or application
- Enforce rate limits and quotas
- Restrict access to specific features
- Block abusive or compromised clients
They are not a complete security solution, but they are very useful for machine-to-machine communication, public SDKs, and simple services.
Typical examples:
- A mobile app calling your backend includes an API key so you can track requests by app.
- A third party integration (like a payment provider or weather API) gives you an API key to call their API.
- Your own microservice A calls microservice B with an internal API key so B can identify A.
Important:
An API key identifies the caller but does not, by itself, prove who the caller is with strong guarantees. It should be treated as a secret and combined with other mechanisms when strong security is required.
What an API Key Looks Like
API keys are just strings, but good keys follow some rules:
- Long enough to be hard to guess
- Random, not predictable
- Opaque, contain no meaningful data inside
Common formats:
| Format | Example |
|---|---|
| Random hex | cafe9d2bce4a457aa97a8f0d9e844014 |
| Base64-like | Y2R3G3Y6sV4S4p2Iu6l0T2g8r9L1s0k0== |
| Prefixed random string | sk_live_4f29b0c3e0764e17a2fad3d9f9e39f15 |
| UUID-like | 3f7bfaf0-ff4c-4a8d-9e2b-4a62e0c89f87 |
Prefixed keys are helpful. For example:
pub_prefix for public keys that can be exposed in frontendsk_prefix for secret keys that must stay in the backendtest_/live_prefixes for environment separation
Rule:
Use a cryptographically secure random generator for API keys. Never use incrementing numbers, usernames, emails, or guessable patterns.
API Keys vs Other Auth Methods
API keys are just one way to control access. Compare them with common alternatives:
| Method | Identifies | Typical Use |
|---|---|---|
| API key | Application or client | Service-to-service, public API clients |
| Basic auth | User or app | Very simple, mostly legacy or internal use |
| Session cookie | Logged-in user | Web browsers with server-side sessions |
| JWT | User or client + data | Modern stateless auth for APIs |
| OAuth 2.0 | User or app | Third party access, social login |
API keys are usually:
- Easier to implement than OAuth or complex token systems
- Worse than modern methods for strong user authentication and delegation
- Fine for:
- Server-to-server APIs
- Internal microservices
- Public APIs where each client has its own key
A common pattern:
- Use API keys to identify and rate-limit each client application
- Use user auth (sessions, JWT, OAuth) to identify the end user
Example: A mobile app sends both:
- An API key that identifies the app installation
- A user token that identifies the logged-in user
Where to Send the API Key
Your API must define where clients send the key. The most common options:
| Location | Example | Notes |
|---|---|---|
| HTTP header | Authorization: Api-Key <key> | Recommended, neat and explicit |
| Custom header | X-API-Key: <key> | Also common, simple to understand |
| Query parameter | GET /resource?api_key=<key> | Avoid for sensitive keys, appears in logs |
| Request body | { "api_key": "<key>" } | Possible for POST, not standard for all methods |
Recommended approach:
- Use an HTTP header, for example:
GET /v1/orders
Authorization: Api-Key sk_live_4f29b0c3e0764e17a2fad3d9f9e39f15Or with a custom header:
GET /v1/orders
X-API-Key: sk_live_4f29b0c3e0764e17a2fad3d9f9e39f15
Rule:
Avoid putting secret API keys in URLs (query parameters). They can be logged in web server logs, browser history, and analytics.
Designing API Key Permissions
API keys can be more than "on/off". You can attach scopes or permissions to each key.
Examples:
- Read-only vs read-write keys
- Keys that can only access specific resources or endpoints
- Environment-specific keys (test vs production)
- Per-customer or per-application keys
You can model keys with fields like:
| Field | Example | Purpose |
|---|---|---|
id | 123 | Internal database ID |
key_hash | Hash of the key string | Secure storage |
owner_id | user_id or org_id | Who owns this key |
scopes | ["orders:read", "orders:write"] | Permissions |
created_at | 2026-08-15T12:00:00Z | Audit |
last_used_at | 2026-08-20T08:30:00Z | Monitoring, cleanup |
expires_at | 2027-08-15T12:00:00Z | Automatic expiry |
revoked | true / false | Soft deletion or manual revocation |
From the API side, you can enforce permissions like:
- Check that the key is valid and not expired
- Check that it has the required scope for the endpoint
- Limit speed or max calls per time window per key
Generating API Keys Securely
In a backend, key generation should use strong randomness. In Python:
import secrets
def generate_api_key(prefix: str = "sk") -> str:
# 32 bytes = 256 bits of entropy, then hex-encode
random_bytes = secrets.token_hex(32)
return f"{prefix}_{random_bytes}"Example generated key:
sk_4e512e50fa624e94a2806fb53af5abdb60bcfc0e553441a493da694d5fa678b7You can generate different types of keys:
def generate_public_key() -> str:
return generate_api_key(prefix="pub")
def generate_secret_key() -> str:
return generate_api_key(prefix="sk")
def generate_test_key() -> str:
return generate_api_key(prefix="test")
Rule:
Use secrets or similar cryptographically secure generators, not random.random() or simple UUIDs for security-critical keys.
Storing and Validating API Keys
You should not store API keys in plain text. Treat them like passwords.
A simple process:
- Generate the key.
- Store only a hash of the key in your database.
- Show the full key to the user only once at creation time.
- For every request:
- Extract the key from the header.
- Hash the received key.
- Look up the hash in the database.
Example with Python and a simple hash (for illustration):
import hashlib
def hash_api_key(key: str) -> str:
return hashlib.sha256(key.encode("utf-8")).hexdigest()Database table example:
| id | owner_id | key_hash | scopes | revoked | created_at |
|---|---|---|---|---|---|
| 1 | 42 | 7a1a... (SHA-256 of full key) | ["orders:read"] | false | 2026-08-20T10:00:00 |
Validation logic (simplified):
def authenticate_api_key(raw_key: str) -> dict | None:
key_hash = hash_api_key(raw_key)
# pseudo-code DB query
record = db.api_keys.find_one({"key_hash": key_hash, "revoked": False})
if record is None:
return None
return record # contains owner, scopes, etc.Then, in your request handling:
def get_api_key_from_headers(headers: dict) -> str | None:
# Example: Authorization: Api-Key <key>
auth = headers.get("Authorization")
if not auth:
return None
try:
scheme, value = auth.split(" ", 1)
except ValueError:
return None
if scheme != "Api-Key":
return None
return value.strip()And:
def handle_request(headers: dict):
raw_key = get_api_key_from_headers(headers)
if raw_key is None:
return {"status": 401, "message": "Missing API key"}
api_key_record = authenticate_api_key(raw_key)
if api_key_record is None:
return {"status": 403, "message": "Invalid or revoked API key"}
# Continue handling the request, applying scopes and rate limits
Rule:
Never log full API keys. At most, log a short prefix like the first 6 characters and the last 4 characters.
Rate Limiting and Usage Tracking Per Key
API keys are very useful for per-client rate limits and quotas.
You can track usage:
- Total number of calls for billing
- Number of errors, for monitoring
- Requests per minute per key, to prevent abuse
Example logic:
- For each incoming request, after validating the key:
- Build a key like:
rate:<api_key_id>:<current_minute> - Increment a counter in a fast store like Redis.
- If the counter goes over a fixed limit, return
429 Too Many Requests.
Pseudo-code:
def is_rate_limited(api_key_id: int, max_per_minute: int) -> bool:
current_minute = get_current_minute_bucket() # e.g. "2026-08-28T09:23"
redis_key = f"rate:{api_key_id}:{current_minute}"
count = redis.incr(redis_key)
if count == 1:
redis.expire(redis_key, 60) # auto reset after 60 seconds
return count > max_per_minuteYou can apply more advanced schemes later, such as sliding windows or separate limits per endpoint.
Revoking and Rotating API Keys
Keys will eventually:
- Be leaked
- Need stricter permissions
- Need to be removed
So you must support:
- Revocation: Mark a key as invalid so it can no longer be used.
- Rotation: Replace an old key with a new one, usually while keeping both valid for a short period.
Basic policies:
- Allow users to create multiple API keys.
- Allow users to delete or revoke keys themselves.
- Expire keys that have not been used for a long time.
- Encourage clients to handle rotated keys gracefully.
Example rotation process:
- User requests a new key.
- System generates a new key and stores its hash.
- System keeps the old key valid for a migration window.
- After the window, system revokes the old key.
A table might look like:
| id | owner_id | key_hash | revoked | expires_at |
|---|---|---|---|---|
| 1 | 42 | aaa... | false | 2026-12-31T23:59:59Z |
| 2 | 42 | bbb... | true | 2025-12-31T23:59:59Z |
Rule:
Plan for key revocation from day one. If you cannot revoke, you cannot recover from a leak safely.
Security Best Practices for API Keys
Even though you will cover broader security topics elsewhere, there are some specific practices that matter for API keys:
- Always use HTTPS
- API keys must never cross the network in plain text.
- Configure your API to reject plain HTTP in production.
- Separate test and production keys
- Test keys must never have access to real user data.
- Use clear prefixes, for example:
test_vslive_. - Limit what each key can do
- Least privilege: give only the permissions needed.
- Make read-only keys when possible.
- Do not put secret keys in frontend code
- Anything in JavaScript, HTML, or mobile app binary is visible to users.
- Only use public keys in frontend, and lock them down on the backend.
- Store keys securely in your own infrastructure
- Use environment variables or secret management solutions for keys you use to call external services.
- Avoid checking keys into Git or configuration files in version control.
- Monitor key usage
- Detect unusual patterns, for example:
- Sudden spike of calls from a key.
- Calls from unexpected regions.
- Be ready to automatically revoke or throttle.
- Document the API key scheme clearly for clients
- Explain where to put the key (header, format).
- Explain rate limits and quotas.
- Show example requests.
Example documentation snippet:
Authentication
All requests must include a secret API key in the Authorization header.
Header format:
Authorization: Api-Key <your-secret-key>
Example:
curl -H "Authorization: Api-Key sk_live_..." https://api.example.com/v1/ordersExample: Simple API Key Middleware (Conceptual)
To make the idea concrete, here is a very simplified FastAPI-style example of API key protection. The real FastAPI chapter will show the full details, but this gives you an idea.
from fastapi import FastAPI, Request, HTTPException, status
app = FastAPI()
FAKE_DB = {
# key_hash -> record
}
def get_api_key_from_request(request: Request) -> str | None:
auth = request.headers.get("Authorization")
if not auth:
return None
try:
scheme, key = auth.split(" ", 1)
except ValueError:
return None
if scheme != "Api-Key":
return None
return key.strip()
@app.middleware("http")
async def api_key_middleware(request: Request, call_next):
raw_key = get_api_key_from_request(request)
if raw_key is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing API key",
)
key_hash = hash_api_key(raw_key)
api_key_record = FAKE_DB.get(key_hash)
if not api_key_record or api_key_record["revoked"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or revoked API key",
)
# Attach the key record to the request for later handlers
request.state.api_key = api_key_record
response = await call_next(request)
return response
Any route behind this middleware will require a valid API key. Inside your route handlers you can access request.state.api_key to check scopes or owner.
When Not to Use API Keys Alone
There are situations where relying only on an API key is not enough:
- You have end users who log in, need passwords, and have personal data.
- You need strong proof of user identity, not just which app is calling you.
- You want third party applications to access user accounts with consent and limited access.
In those cases, you will usually combine:
- API keys for identifying the client application.
- User authentication (sessions, JWT, OAuth 2.0) for identifying the user.
For example, a payment provider might:
- Give your backend a secret API key to access their API.
- Give your frontend a public key used only to generate tokens that represent card data.
You will see more advanced patterns, such as OAuth 2.0 and JWT, in other chapters. The key idea here is to know what API keys are good at, and where their limits are.
Summary Rule:
Use API keys for client identification, rate limiting, and simple access control, but do not treat them as a full replacement for robust user authentication and authorization.
Views: 10
KAHIBARO