KAHIBARO
Discord Login Register

27.7. API Keys

Why API Keys Exist

API keys are simple strings that identify who is calling an API. They are usually used to:

They are not a complete security solution, but they are very useful for machine-to-machine communication, public SDKs, and simple services.

Typical examples:

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:

Common formats:

FormatExample
Random hexcafe9d2bce4a457aa97a8f0d9e844014
Base64-likeY2R3G3Y6sV4S4p2Iu6l0T2g8r9L1s0k0==
Prefixed random stringsk_live_4f29b0c3e0764e17a2fad3d9f9e39f15
UUID-like3f7bfaf0-ff4c-4a8d-9e2b-4a62e0c89f87

Prefixed keys are helpful. For example:

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:

MethodIdentifiesTypical Use
API keyApplication or clientService-to-service, public API clients
Basic authUser or appVery simple, mostly legacy or internal use
Session cookieLogged-in userWeb browsers with server-side sessions
JWTUser or client + dataModern stateless auth for APIs
OAuth 2.0User or appThird party access, social login

API keys are usually:

A common pattern:

Example: A mobile app sends both:

Where to Send the API Key

Your API must define where clients send the key. The most common options:

LocationExampleNotes
HTTP headerAuthorization: Api-Key <key>Recommended, neat and explicit
Custom headerX-API-Key: <key>Also common, simple to understand
Query parameterGET /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:

http
  GET /v1/orders
  Authorization: Api-Key sk_live_4f29b0c3e0764e17a2fad3d9f9e39f15

Or with a custom header:

http
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:

You can model keys with fields like:

FieldExamplePurpose
id123Internal database ID
key_hashHash of the key stringSecure storage
owner_iduser_id or org_idWho owns this key
scopes["orders:read", "orders:write"]Permissions
created_at2026-08-15T12:00:00ZAudit
last_used_at2026-08-20T08:30:00ZMonitoring, cleanup
expires_at2027-08-15T12:00:00ZAutomatic expiry
revokedtrue / falseSoft deletion or manual revocation

From the API side, you can enforce permissions like:

Generating API Keys Securely

In a backend, key generation should use strong randomness. In Python:

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:

text
sk_4e512e50fa624e94a2806fb53af5abdb60bcfc0e553441a493da694d5fa678b7

You can generate different types of keys:

python
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:

  1. Generate the key.
  2. Store only a hash of the key in your database.
  3. Show the full key to the user only once at creation time.
  4. 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):

python
import hashlib
def hash_api_key(key: str) -> str:
    return hashlib.sha256(key.encode("utf-8")).hexdigest()

Database table example:

idowner_idkey_hashscopesrevokedcreated_at
1427a1a... (SHA-256 of full key)["orders:read"]false2026-08-20T10:00:00

Validation logic (simplified):

python
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:

python
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:

python
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:

Example logic:

  1. 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.
  2. If the counter goes over a fixed limit, return 429 Too Many Requests.

Pseudo-code:

python
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_minute

You can apply more advanced schemes later, such as sliding windows or separate limits per endpoint.

Revoking and Rotating API Keys

Keys will eventually:

So you must support:

Basic policies:

Example rotation process:

  1. User requests a new key.
  2. System generates a new key and stores its hash.
  3. System keeps the old key valid for a migration window.
  4. After the window, system revokes the old key.

A table might look like:

idowner_idkey_hashrevokedexpires_at
142aaa...false2026-12-31T23:59:59Z
242bbb...true2025-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:

  1. Always use HTTPS
    • API keys must never cross the network in plain text.
    • Configure your API to reject plain HTTP in production.
  2. Separate test and production keys
    • Test keys must never have access to real user data.
    • Use clear prefixes, for example: test_ vs live_.
  3. Limit what each key can do
    • Least privilege: give only the permissions needed.
    • Make read-only keys when possible.
  4. 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.
  5. 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.
  6. 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.
  7. 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:

text
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/orders

Example: 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.

python
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:

In those cases, you will usually combine:

For example, a payment provider might:

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

Comments

Please login to add a comment.

Don't have an account? Register now!