Introduction to Security Dependencies
When you want to secure your FastAPI endpoints, you need a way to both validate incoming credentials and ensure those requirements appear correctly in the generated OpenAPI documentation (Swagger UI). Without a structured security system, you would have to manually parse headers or query parameters and manually update your OpenAPI schema.
FastAPI solves this using a hierarchy of security classes rooted in SecurityBase.
The Role of SecurityBase
The SecurityBase class in fastapi/security/base.py is the foundational building block for all authentication schemes in FastAPI. It serves two critical roles:
- OpenAPI Metadata: It stores the
model(an OpenAPI security model) andscheme_nameused to populate thecomponents/securitySchemessection of your API's JSON schema. - Dependency Marker: It acts as a signal to FastAPI's dependency injection system. When you use a class inheriting from
SecurityBasewithDependsorSecurity, FastAPI recognizes it as a security requirement.
class SecurityBase:
model: SecurityBaseModel
scheme_name: str
Using Security Dependencies
To secure an endpoint, you instantiate a security scheme (like APIKeyQuery, HTTPBearer, or OAuth2PasswordBearer) and pass it to a dependency.
In this example, using APIKeyQuery (which inherits from SecurityBase via APIKeyBase) ensures that the "Authorize" button appears in Swagger UI and that the key query parameter is automatically extracted.
from fastapi import Depends, FastAPI, Security
from fastapi.security import APIKeyQuery
app = FastAPI()
# Instantiate the scheme
api_key_scheme = APIKeyQuery(name="key")
@app.get("/users/me")
def read_current_user(api_key: str = Security(api_key_scheme)):
return {"api_key": api_key}
Security vs. Depends
While you can use Depends(api_key_scheme), using Security(api_key_scheme) allows you to define scopes. For many simple schemes, they behave identically, but Security is the preferred way to signal that a dependency is specifically for authentication and authorization.
How Security Schemes Work Internally
Every security scheme in FastAPI follows a specific implementation pattern found in fastapi/security/api_key.py, fastapi/security/http.py, and fastapi/security/oauth2.py.
1. OpenAPI Integration
When you initialize a scheme like APIKeyQuery, it populates the self.model attribute with an APIKey model. FastAPI's OpenAPI generator iterates through all dependencies; if it finds an instance of SecurityBase, it extracts this model to build the security definitions.
2. Request Handling via __call__
For a security scheme to work as a dependency, it must implement the __call__ method. This method receives the starlette.requests.Request and returns the credentials.
For example, APIKeyQuery.__call__ looks like this:
async def __call__(self, request: Request) -> str | None:
api_key = request.query_params.get(self.model.name)
return self.check_api_key(api_key)
3. The auto_error Pattern
Most built-in schemes include an auto_error parameter (defaulting to True). This logic is typically handled in a base class method like check_api_key in APIKeyBase:
def check_api_key(self, api_key: str | None) -> str | None:
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "APIKey"},
)
return None
return api_key
If auto_error is False, the dependency returns None instead of raising a 401 Unauthorized error. This is useful for endpoints where authentication is optional.
Creating Custom Security Schemes
If the built-in schemes don't fit your needs, you can create a custom one by inheriting from SecurityBase. You must provide a model for OpenAPI and implement __call__ for the logic.
from fastapi.security.base import SecurityBase
from fastapi.openapi.models import APIKey, APIKeyIn
from starlette.requests import Request
from starlette.exceptions import HTTPException
class CustomHeaderAuth(SecurityBase):
def __init__(self, *, name: str, auto_error: bool = True):
self.model = APIKey(**{"in": APIKeyIn.header}, name=name)
self.scheme_name = self.__class__.__name__
self.auto_error = auto_error
async def __call__(self, request: Request) -> str | None:
header_value = request.headers.get(self.model.name)
if not header_value and self.auto_error:
raise HTTPException(status_code=403, detail="Missing Custom Header")
return header_value
By inheriting from SecurityBase, your custom class is automatically recognized by FastAPI's OpenAPI generator, ensuring your custom security logic is documented without extra configuration.