| from typing import Annotated, Optional, Union | |
| from annotated_doc import Doc | |
| from fastapi.openapi.models import APIKey, APIKeyIn | |
| from fastapi.security.base import SecurityBase | |
| from starlette.exceptions import HTTPException | |
| from starlette.requests import Request | |
| from starlette.status import HTTP_401_UNAUTHORIZED | |
| class APIKeyBase(SecurityBase): | |
| def __init__( | |
| self, | |
| location: APIKeyIn, | |
| name: str, | |
| description: Union[str, None], | |
| scheme_name: Union[str, None], | |
| auto_error: bool, | |
| ): | |
| self.auto_error = auto_error | |
| self.model: APIKey = APIKey( | |
| **{"in": location}, | |
| name=name, | |
| description=description, | |
| ) | |
| self.scheme_name = scheme_name or self.__class__.__name__ | |
| def make_not_authenticated_error(self) -> HTTPException: | |
| """ | |
| The WWW-Authenticate header is not standardized for API Key authentication but | |
| the HTTP specification requires that an error of 401 "Unauthorized" must | |
| include a WWW-Authenticate header. | |
| Ref: https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized | |
| For this, this method sends a custom challenge `APIKey`. | |
| """ | |
| return HTTPException( | |
| status_code=HTTP_401_UNAUTHORIZED, | |
| detail="Not authenticated", | |
| headers={"WWW-Authenticate": "APIKey"}, | |
| ) | |
| def check_api_key(self, api_key: Optional[str]) -> Optional[str]: | |
| if not api_key: | |
| if self.auto_error: | |
| raise self.make_not_authenticated_error() | |
| return None | |
| return api_key | |
| class APIKeyQuery(APIKeyBase): | |
| """ | |
| API key authentication using a query parameter. | |
| This defines the name of the query parameter that should be provided in the request | |
| with the API key and integrates that into the OpenAPI documentation. It extracts | |
| the key value sent in the query parameter automatically and provides it as the | |
| dependency result. But it doesn't define how to send that API key to the client. | |
| ## Usage | |
| Create an instance object and use that object as the dependency in `Depends()`. | |
| The dependency result will be a string containing the key value. | |
| ## Example | |
| ```python | |
| from fastapi import Depends, FastAPI | |
| from fastapi.security import APIKeyQuery | |
| app = FastAPI() | |
| query_scheme = APIKeyQuery(name="api_key") | |
| @app.get("/items/") | |
| async def read_items(api_key: str = Depends(query_scheme)): | |
| return {"api_key": api_key} | |
| ``` | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| name: Annotated[ | |
| str, | |
| Doc("Query parameter name."), | |
| ], | |
| scheme_name: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme name. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| description: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme description. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| auto_error: Annotated[ | |
| bool, | |
| Doc( | |
| """ | |
| By default, if the query parameter is not provided, `APIKeyQuery` will | |
| automatically cancel the request and send the client an error. | |
| If `auto_error` is set to `False`, when the query parameter is not | |
| available, instead of erroring out, the dependency result will be | |
| `None`. | |
| This is useful when you want to have optional authentication. | |
| It is also useful when you want to have authentication that can be | |
| provided in one of multiple optional ways (for example, in a query | |
| parameter or in an HTTP Bearer token). | |
| """ | |
| ), | |
| ] = True, | |
| ): | |
| super().__init__( | |
| location=APIKeyIn.query, | |
| name=name, | |
| scheme_name=scheme_name, | |
| description=description, | |
| auto_error=auto_error, | |
| ) | |
| async def __call__(self, request: Request) -> Optional[str]: | |
| api_key = request.query_params.get(self.model.name) | |
| return self.check_api_key(api_key) | |
| class APIKeyHeader(APIKeyBase): | |
| """ | |
| API key authentication using a header. | |
| This defines the name of the header that should be provided in the request with | |
| the API key and integrates that into the OpenAPI documentation. It extracts | |
| the key value sent in the header automatically and provides it as the dependency | |
| result. But it doesn't define how to send that key to the client. | |
| ## Usage | |
| Create an instance object and use that object as the dependency in `Depends()`. | |
| The dependency result will be a string containing the key value. | |
| ## Example | |
| ```python | |
| from fastapi import Depends, FastAPI | |
| from fastapi.security import APIKeyHeader | |
| app = FastAPI() | |
| header_scheme = APIKeyHeader(name="x-key") | |
| @app.get("/items/") | |
| async def read_items(key: str = Depends(header_scheme)): | |
| return {"key": key} | |
| ``` | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| name: Annotated[str, Doc("Header name.")], | |
| scheme_name: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme name. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| description: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme description. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| auto_error: Annotated[ | |
| bool, | |
| Doc( | |
| """ | |
| By default, if the header is not provided, `APIKeyHeader` will | |
| automatically cancel the request and send the client an error. | |
| If `auto_error` is set to `False`, when the header is not available, | |
| instead of erroring out, the dependency result will be `None`. | |
| This is useful when you want to have optional authentication. | |
| It is also useful when you want to have authentication that can be | |
| provided in one of multiple optional ways (for example, in a header or | |
| in an HTTP Bearer token). | |
| """ | |
| ), | |
| ] = True, | |
| ): | |
| super().__init__( | |
| location=APIKeyIn.header, | |
| name=name, | |
| scheme_name=scheme_name, | |
| description=description, | |
| auto_error=auto_error, | |
| ) | |
| async def __call__(self, request: Request) -> Optional[str]: | |
| api_key = request.headers.get(self.model.name) | |
| return self.check_api_key(api_key) | |
| class APIKeyCookie(APIKeyBase): | |
| """ | |
| API key authentication using a cookie. | |
| This defines the name of the cookie that should be provided in the request with | |
| the API key and integrates that into the OpenAPI documentation. It extracts | |
| the key value sent in the cookie automatically and provides it as the dependency | |
| result. But it doesn't define how to set that cookie. | |
| ## Usage | |
| Create an instance object and use that object as the dependency in `Depends()`. | |
| The dependency result will be a string containing the key value. | |
| ## Example | |
| ```python | |
| from fastapi import Depends, FastAPI | |
| from fastapi.security import APIKeyCookie | |
| app = FastAPI() | |
| cookie_scheme = APIKeyCookie(name="session") | |
| @app.get("/items/") | |
| async def read_items(session: str = Depends(cookie_scheme)): | |
| return {"session": session} | |
| ``` | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| name: Annotated[str, Doc("Cookie name.")], | |
| scheme_name: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme name. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| description: Annotated[ | |
| Optional[str], | |
| Doc( | |
| """ | |
| Security scheme description. | |
| It will be included in the generated OpenAPI (e.g. visible at `/docs`). | |
| """ | |
| ), | |
| ] = None, | |
| auto_error: Annotated[ | |
| bool, | |
| Doc( | |
| """ | |
| By default, if the cookie is not provided, `APIKeyCookie` will | |
| automatically cancel the request and send the client an error. | |
| If `auto_error` is set to `False`, when the cookie is not available, | |
| instead of erroring out, the dependency result will be `None`. | |
| This is useful when you want to have optional authentication. | |
| It is also useful when you want to have authentication that can be | |
| provided in one of multiple optional ways (for example, in a cookie or | |
| in an HTTP Bearer token). | |
| """ | |
| ), | |
| ] = True, | |
| ): | |
| super().__init__( | |
| location=APIKeyIn.cookie, | |
| name=name, | |
| scheme_name=scheme_name, | |
| description=description, | |
| auto_error=auto_error, | |
| ) | |
| async def __call__(self, request: Request) -> Optional[str]: | |
| api_key = request.cookies.get(self.model.name) | |
| return self.check_api_key(api_key) | |