At the moment, getting OpenAPI and multi-api-key dependencies to play nicely seems to be an outstanding problem as seen in this issue.
There are a few ways to handle it, the most straightforward of which is creating two APIKeyHeader dependencies and using them both in each request. You don't actually need to do any inheriting from the APIKeyBase class, as the APIKeyHeader class that FastAPI provides should do the trick on its own.
I would also probably define an additional dependency that uses the other two as sub-dependencies like so:
from typing import Optional
from fastapi import FastAPI, Depends, Security, HTTPException, status
from fastapi.security.api_key import APIKeyHeader
app = FastAPI()
class HMACAuth:
async def __call__(
self,
x_hmac_api_key: str = Depends(APIKeyHeader(name="x-hmac-api-key", scheme_name="HMACApiKey")),
x_hmac_signature: str = Depends(APIKeyHeader(name="x-hmac-signature", scheme_name="HMACSignature")),
) -> Optional[str]:
if x_hmac_api_key and x_hmac_signature:
# do whatever check is needed here
if x_hmac_api_key in ["valid-api-key"] and x_hmac_signature in ["valid-signature"]:
return x_hmac_api_key
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authenticated",
)
@app.get(
"/",
response_model=str,
name="app:test-api",
dependencies=[Security(HMACAuth())],
)
async def test_api() -> str:
return "success"
You would then need to override your app's openapi method as explained in the extending OpenAPI section of the docs.
The reason being that any route with this dependency would have different OpenAPI security schema generated for each sub-dependency. Meaning users would be able to enter in a value for either on in the OpenAPI docs and be "authenticated".
To remedy that, you could do something like this:
from fastapi.openapi.utils import get_openapi
# ...previous code
def merge_hmac_securities(list_of_securities: List[Dict[str, List]]) -> List[dict]:
"""
Find HMACApiKey and HMACSignature securities and merge them
"""
original_securities = []
hmac_security = {}
for security in list_of_securities:
if "HMACApiKey" in security or "HMACSignature" in security:
hmac_security.update(security)
else:
original_securities.append(security)
return original_securities + ([hmac_security] if hmac_security else [])
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="3.0.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
for path in openapi_schema.get("paths"):
for http_method in openapi_schema["paths"][path]:
list_of_securities = openapi_schema["paths"][path][http_method].get("security")
if list_of_securities:
merged_list_of_securities = merge_hmac_securities(list_of_securities)
openapi_schema["paths"][path][http_method]["security"] = merged_list_of_securities
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
Doing that should solve both of your problems.