J-O Eriksson's Blog

FastAPI API Key Authentication with Security and Depends

How to secure a FastAPI app with header-based API key authentication using APIKeyHeader, Security, and Depends.

Introduction

Sometimes you build an API that should only be accessible to clients that present a valid key. FastAPI makes this straightforward with its built-in APIKeyHeader and dependency injection system.

The dependency

# api/core.py

import os
from fastapi import HTTPException, Security
from fastapi.security import APIKeyHeader
from starlette.status import HTTP_403_FORBIDDEN

API_KEY = os.environ['ApiKey']
API_KEY_NAME = os.environ['ApiKeyName']

api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)

async def get_api_key(
    api_key_header: str = Security(api_key_header),
) -> str:
    if api_key_header == API_KEY:
        return api_key_header
    raise HTTPException(HTTP_403_FORBIDDEN, detail='Could not validate credentials')

Walking through what each part does:

  • API_KEY and API_KEY_NAME are read from environment variables so the secret never lives in code. API_KEY_NAME is the name of the HTTP header that clients must send (e.g. X-API-Key).
  • APIKeyHeader(name=API_KEY_NAME, auto_error=False) creates a FastAPI security scheme that knows to extract the value from that header on every request. Setting auto_error=False means FastAPI won’t immediately return a 422 if the header is missing — it instead passes None to get_api_key so we can raise a more meaningful 403 ourselves.
  • Security(api_key_header) inside the function signature tells FastAPI to resolve the header value and inject it as api_key_header. Using Security rather than Depends also registers the scheme in the OpenAPI spec, so the “Authorize” button appears in the interactive /docs.
  • The if check compares the incoming header value against the expected key. A match returns the key (which makes it available to the route handler via Depends); anything else raises a 403 Forbidden.

Protecting an endpoint

Add the get_api_key function as a dependency on any route you want to guard:

# groups.py

from api.core import get_api_key
from fastapi import Depends

@router.get("/{id}/")
async def read_group(id: str, api_key: str = Depends(get_api_key)):
    ...

Any request without the correct header value will be rejected before your handler runs. Clients that do pass the right key get through and the key value is available as api_key if you need it.

Conclusion

One of the nice things about this pattern is how well it composes. Define get_api_key once and reuse it across as many routes as you need with Depends. If you later want to move to JWT or OAuth2, you only need to swap out that single dependency — your route handlers stay untouched.

Resources