I've been using https://github.com/intility/fastapi-azure-auth to authenticate my uses using Azure. It's working with no issues. But for some api endpoints I need to allow users to access via API key inside the headers. I have the following code:
The unprotected endpoint:
@router.get("/", response_model=list[ContractSchema])
def get_contracts(
filters: ContractFilter = Depends(),
db: Session = Depends(get_db),
user: User = Depends(dual_authentication),
) -> list[ContractSchema]:
return []
the configuration of Azure auth and the scheme:
from fastapi_azure_auth.auth import SingleTenantAzureAuthorizationCodeBearer
from fastapi_azure_auth.user import User
azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
app_client_id=settings.azure_ad.app_client_id,
tenant_id=settings.azure_ad.tenant_id,
token_version=1,
scopes={f"api://{settings.azure_ad.app_client_id}/user_impersonation": "user_impersonation"},
)
api_key_scheme = APIKeyHeader(name="x-api-key", auto_error=False)
protected_dependencies = [Security(azure_scheme, scopes=["user_impersonation"])]
def dual_authentication(
api_key: Optional[str] = Security(api_key_scheme),
azure_token: Optional[User] = Depends(azure_scheme)
) -> User:
"""Authenticate via API key or Azure AD."""
# Check for API key in the header and validate
if _is_api_key_present_and_valid(api_key):
return EXTERNAL_API_USER
# Fall back to Azure AD authentication
if azure_token:
return azure_token
# If neither is valid, raise an error
raise HTTPException(status_code=401, detail="User not authenticated")
This setup works fine with Swagger as well as using curl requests with Authorization Bearer token from Azure inside headers.
But when I do curl request to the endpoint with x-api-key
in the headers without Authorization Bearer token from Azure, it gives me 401 Unauthorized error.
I know this is normal as I put Depends(azure_scheme)
in the dual_authentication
method. But if I remove this, then from swagger I can't access the endpoint even though I authorize using Azure. It's because, then the Authorization Bearer token is not passed in headers for this endpoint.
How to resolve that, so that the endpoint accepts either of the mechanism from Swagger and Curl requests? Which means,
The folder structure:
The configuration is in dependencies.py
file. The route is in api/v1/contracts.py
file.
The default OAuth schemes included with FastAPI has the auto_error
configuration argument to say whether the bearer dependency should generate an error (the default) or return None
instead (and let you handle the failed authentication).
The same is the case for SingleTenantAzureAuthorizationCodeBearer
. This allows you to define a second azure scheme that allows authentication to fail:
azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
app_client_id=settings.azure_ad.app_client_id,
tenant_id=settings.azure_ad.tenant_id,
token_version=1,
scopes={f"api://{settings.azure_ad.app_client_id}/user_impersonation": "user_impersonation"},
)
azure_scheme_allow_unauthenticated = SingleTenantAzureAuthorizationCodeBearer(
auto_error=False, # <------
app_client_id=settings.azure_ad.app_client_id,
tenant_id=settings.azure_ad.tenant_id,
token_version=1,
scopes={f"api://{settings.azure_ad.app_client_id}/user_impersonation": "user_impersonation"},
)
You can then depend on this other scheme in your dual authentication function:
def dual_authentication(
api_key: Optional[str] = Security(api_key_scheme),
azure_token: Optional[User] = Depends(azure_scheme_allow_unauthenticated)
) -> User:
# raise exception if neither authentication is valid (as it seems you're doing already)