api_key.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. from typing import Optional
  2. from fastapi.openapi.models import APIKey, APIKeyIn
  3. from fastapi.security.base import SecurityBase
  4. from starlette.exceptions import HTTPException
  5. from starlette.requests import Request
  6. from starlette.status import HTTP_403_FORBIDDEN
  7. from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
  8. class APIKeyBase(SecurityBase):
  9. pass
  10. class APIKeyQuery(APIKeyBase):
  11. """
  12. API key authentication using a query parameter.
  13. This defines the name of the query parameter that should be provided in the request
  14. with the API key and integrates that into the OpenAPI documentation. It extracts
  15. the key value sent in the query parameter automatically and provides it as the
  16. dependency result. But it doesn't define how to send that API key to the client.
  17. ## Usage
  18. Create an instance object and use that object as the dependency in `Depends()`.
  19. The dependency result will be a string containing the key value.
  20. ## Example
  21. ```python
  22. from fastapi import Depends, FastAPI
  23. from fastapi.security import APIKeyQuery
  24. app = FastAPI()
  25. query_scheme = APIKeyQuery(name="api_key")
  26. @app.get("/items/")
  27. async def read_items(api_key: str = Depends(query_scheme)):
  28. return {"api_key": api_key}
  29. ```
  30. """
  31. def __init__(
  32. self,
  33. *,
  34. name: Annotated[
  35. str,
  36. Doc("Query parameter name."),
  37. ],
  38. scheme_name: Annotated[
  39. Optional[str],
  40. Doc(
  41. """
  42. Security scheme name.
  43. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  44. """
  45. ),
  46. ] = None,
  47. description: Annotated[
  48. Optional[str],
  49. Doc(
  50. """
  51. Security scheme description.
  52. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  53. """
  54. ),
  55. ] = None,
  56. auto_error: Annotated[
  57. bool,
  58. Doc(
  59. """
  60. By default, if the query parameter is not provided, `APIKeyQuery` will
  61. automatically cancel the request and sebd the client an error.
  62. If `auto_error` is set to `False`, when the query parameter is not
  63. available, instead of erroring out, the dependency result will be
  64. `None`.
  65. This is useful when you want to have optional authentication.
  66. It is also useful when you want to have authentication that can be
  67. provided in one of multiple optional ways (for example, in a query
  68. parameter or in an HTTP Bearer token).
  69. """
  70. ),
  71. ] = True,
  72. ):
  73. self.model: APIKey = APIKey(
  74. **{"in": APIKeyIn.query}, # type: ignore[arg-type]
  75. name=name,
  76. description=description,
  77. )
  78. self.scheme_name = scheme_name or self.__class__.__name__
  79. self.auto_error = auto_error
  80. async def __call__(self, request: Request) -> Optional[str]:
  81. api_key = request.query_params.get(self.model.name)
  82. if not api_key:
  83. if self.auto_error:
  84. raise HTTPException(
  85. status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
  86. )
  87. else:
  88. return None
  89. return api_key
  90. class APIKeyHeader(APIKeyBase):
  91. """
  92. API key authentication using a header.
  93. This defines the name of the header that should be provided in the request with
  94. the API key and integrates that into the OpenAPI documentation. It extracts
  95. the key value sent in the header automatically and provides it as the dependency
  96. result. But it doesn't define how to send that key to the client.
  97. ## Usage
  98. Create an instance object and use that object as the dependency in `Depends()`.
  99. The dependency result will be a string containing the key value.
  100. ## Example
  101. ```python
  102. from fastapi import Depends, FastAPI
  103. from fastapi.security import APIKeyHeader
  104. app = FastAPI()
  105. header_scheme = APIKeyHeader(name="x-key")
  106. @app.get("/items/")
  107. async def read_items(key: str = Depends(header_scheme)):
  108. return {"key": key}
  109. ```
  110. """
  111. def __init__(
  112. self,
  113. *,
  114. name: Annotated[str, Doc("Header name.")],
  115. scheme_name: Annotated[
  116. Optional[str],
  117. Doc(
  118. """
  119. Security scheme name.
  120. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  121. """
  122. ),
  123. ] = None,
  124. description: Annotated[
  125. Optional[str],
  126. Doc(
  127. """
  128. Security scheme description.
  129. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  130. """
  131. ),
  132. ] = None,
  133. auto_error: Annotated[
  134. bool,
  135. Doc(
  136. """
  137. By default, if the header is not provided, `APIKeyHeader` will
  138. automatically cancel the request and send the client an error.
  139. If `auto_error` is set to `False`, when the header is not available,
  140. instead of erroring out, the dependency result will be `None`.
  141. This is useful when you want to have optional authentication.
  142. It is also useful when you want to have authentication that can be
  143. provided in one of multiple optional ways (for example, in a header or
  144. in an HTTP Bearer token).
  145. """
  146. ),
  147. ] = True,
  148. ):
  149. self.model: APIKey = APIKey(
  150. **{"in": APIKeyIn.header}, # type: ignore[arg-type]
  151. name=name,
  152. description=description,
  153. )
  154. self.scheme_name = scheme_name or self.__class__.__name__
  155. self.auto_error = auto_error
  156. async def __call__(self, request: Request) -> Optional[str]:
  157. api_key = request.headers.get(self.model.name)
  158. if not api_key:
  159. if self.auto_error:
  160. raise HTTPException(
  161. status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
  162. )
  163. else:
  164. return None
  165. return api_key
  166. class APIKeyCookie(APIKeyBase):
  167. """
  168. API key authentication using a cookie.
  169. This defines the name of the cookie that should be provided in the request with
  170. the API key and integrates that into the OpenAPI documentation. It extracts
  171. the key value sent in the cookie automatically and provides it as the dependency
  172. result. But it doesn't define how to set that cookie.
  173. ## Usage
  174. Create an instance object and use that object as the dependency in `Depends()`.
  175. The dependency result will be a string containing the key value.
  176. ## Example
  177. ```python
  178. from fastapi import Depends, FastAPI
  179. from fastapi.security import APIKeyCookie
  180. app = FastAPI()
  181. cookie_scheme = APIKeyCookie(name="session")
  182. @app.get("/items/")
  183. async def read_items(session: str = Depends(cookie_scheme)):
  184. return {"session": session}
  185. ```
  186. """
  187. def __init__(
  188. self,
  189. *,
  190. name: Annotated[str, Doc("Cookie name.")],
  191. scheme_name: Annotated[
  192. Optional[str],
  193. Doc(
  194. """
  195. Security scheme name.
  196. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  197. """
  198. ),
  199. ] = None,
  200. description: Annotated[
  201. Optional[str],
  202. Doc(
  203. """
  204. Security scheme description.
  205. It will be included in the generated OpenAPI (e.g. visible at `/docs`).
  206. """
  207. ),
  208. ] = None,
  209. auto_error: Annotated[
  210. bool,
  211. Doc(
  212. """
  213. By default, if the cookie is not provided, `APIKeyCookie` will
  214. automatically cancel the request and send the client an error.
  215. If `auto_error` is set to `False`, when the cookie is not available,
  216. instead of erroring out, the dependency result will be `None`.
  217. This is useful when you want to have optional authentication.
  218. It is also useful when you want to have authentication that can be
  219. provided in one of multiple optional ways (for example, in a cookie or
  220. in an HTTP Bearer token).
  221. """
  222. ),
  223. ] = True,
  224. ):
  225. self.model: APIKey = APIKey(
  226. **{"in": APIKeyIn.cookie}, # type: ignore[arg-type]
  227. name=name,
  228. description=description,
  229. )
  230. self.scheme_name = scheme_name or self.__class__.__name__
  231. self.auto_error = auto_error
  232. async def __call__(self, request: Request) -> Optional[str]:
  233. api_key = request.cookies.get(self.model.name)
  234. if not api_key:
  235. if self.auto_error:
  236. raise HTTPException(
  237. status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
  238. )
  239. else:
  240. return None
  241. return api_key