Coverage for fastapi / security / api_key.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1from typing import Annotated 1abcd

2 

3from annotated_doc import Doc 1abcd

4from fastapi.openapi.models import APIKey, APIKeyIn 1abcd

5from fastapi.security.base import SecurityBase 1abcd

6from starlette.exceptions import HTTPException 1abcd

7from starlette.requests import Request 1abcd

8from starlette.status import HTTP_401_UNAUTHORIZED 1abcd

9 

10 

11class APIKeyBase(SecurityBase): 1abcd

12 def __init__( 1abcd

13 self, 

14 location: APIKeyIn, 

15 name: str, 

16 description: str | None, 

17 scheme_name: str | None, 

18 auto_error: bool, 

19 ): 

20 self.auto_error = auto_error 1abcd

21 

22 self.model: APIKey = APIKey( 1abcd

23 **{"in": location}, 

24 name=name, 

25 description=description, 

26 ) 

27 self.scheme_name = scheme_name or self.__class__.__name__ 1abcd

28 

29 def make_not_authenticated_error(self) -> HTTPException: 1abcd

30 """ 

31 The WWW-Authenticate header is not standardized for API Key authentication but 

32 the HTTP specification requires that an error of 401 "Unauthorized" must 

33 include a WWW-Authenticate header. 

34 

35 Ref: https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized 

36 

37 For this, this method sends a custom challenge `APIKey`. 

38 """ 

39 return HTTPException( 1efghijklmnopqrstuv

40 status_code=HTTP_401_UNAUTHORIZED, 

41 detail="Not authenticated", 

42 headers={"WWW-Authenticate": "APIKey"}, 

43 ) 

44 

45 def check_api_key(self, api_key: str | None) -> str | None: 1abcd

46 if not api_key: 1FeGfHwIgJhKxLiMjNyOkPlQzRmSnTAUoVpWBXqYrZC0s1t2D3u4v5E

47 if self.auto_error: 1efwghxijyklzmnAopBqrCstDuvE

48 raise self.make_not_authenticated_error() 1efghijklmnopqrstuv

49 return None 1wxyzABCDE

50 return api_key 1FGHIJKLMNOPQRSTUVWXYZ012345

51 

52 

53class APIKeyQuery(APIKeyBase): 1abcd

54 """ 

55 API key authentication using a query parameter. 

56 

57 This defines the name of the query parameter that should be provided in the request 

58 with the API key and integrates that into the OpenAPI documentation. It extracts 

59 the key value sent in the query parameter automatically and provides it as the 

60 dependency result. But it doesn't define how to send that API key to the client. 

61 

62 ## Usage 

63 

64 Create an instance object and use that object as the dependency in `Depends()`. 

65 

66 The dependency result will be a string containing the key value. 

67 

68 ## Example 

69 

70 ```python 

71 from fastapi import Depends, FastAPI 

72 from fastapi.security import APIKeyQuery 

73 

74 app = FastAPI() 

75 

76 query_scheme = APIKeyQuery(name="api_key") 

77 

78 

79 @app.get("/items/") 

80 async def read_items(api_key: str = Depends(query_scheme)): 

81 return {"api_key": api_key} 

82 ``` 

83 """ 

84 

85 def __init__( 1abcd

86 self, 

87 *, 

88 name: Annotated[ 

89 str, 

90 Doc("Query parameter name."), 

91 ], 

92 scheme_name: Annotated[ 

93 str | None, 

94 Doc( 

95 """ 

96 Security scheme name. 

97 

98 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

99 """ 

100 ), 

101 ] = None, 

102 description: Annotated[ 

103 str | None, 

104 Doc( 

105 """ 

106 Security scheme description. 

107 

108 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

109 """ 

110 ), 

111 ] = None, 

112 auto_error: Annotated[ 

113 bool, 

114 Doc( 

115 """ 

116 By default, if the query parameter is not provided, `APIKeyQuery` will 

117 automatically cancel the request and send the client an error. 

118 

119 If `auto_error` is set to `False`, when the query parameter is not 

120 available, instead of erroring out, the dependency result will be 

121 `None`. 

122 

123 This is useful when you want to have optional authentication. 

124 

125 It is also useful when you want to have authentication that can be 

126 provided in one of multiple optional ways (for example, in a query 

127 parameter or in an HTTP Bearer token). 

128 """ 

129 ), 

130 ] = True, 

131 ): 

132 super().__init__( 1abcd

133 location=APIKeyIn.query, 

134 name=name, 

135 scheme_name=scheme_name, 

136 description=description, 

137 auto_error=auto_error, 

138 ) 

139 

140 async def __call__(self, request: Request) -> str | None: 1abcd

141 api_key = request.query_params.get(self.model.name) 1LiMjNyUoVpWB3u4v5E

142 return self.check_api_key(api_key) 1LiMjNyUoVpWB3u4v5E

143 

144 

145class APIKeyHeader(APIKeyBase): 1abcd

146 """ 

147 API key authentication using a header. 

148 

149 This defines the name of the header that should be provided in the request with 

150 the API key and integrates that into the OpenAPI documentation. It extracts 

151 the key value sent in the header automatically and provides it as the dependency 

152 result. But it doesn't define how to send that key to the client. 

153 

154 ## Usage 

155 

156 Create an instance object and use that object as the dependency in `Depends()`. 

157 

158 The dependency result will be a string containing the key value. 

159 

160 ## Example 

161 

162 ```python 

163 from fastapi import Depends, FastAPI 

164 from fastapi.security import APIKeyHeader 

165 

166 app = FastAPI() 

167 

168 header_scheme = APIKeyHeader(name="x-key") 

169 

170 

171 @app.get("/items/") 

172 async def read_items(key: str = Depends(header_scheme)): 

173 return {"key": key} 

174 ``` 

175 """ 

176 

177 def __init__( 1abcd

178 self, 

179 *, 

180 name: Annotated[str, Doc("Header name.")], 

181 scheme_name: Annotated[ 

182 str | None, 

183 Doc( 

184 """ 

185 Security scheme name. 

186 

187 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

188 """ 

189 ), 

190 ] = None, 

191 description: Annotated[ 

192 str | None, 

193 Doc( 

194 """ 

195 Security scheme description. 

196 

197 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

198 """ 

199 ), 

200 ] = None, 

201 auto_error: Annotated[ 

202 bool, 

203 Doc( 

204 """ 

205 By default, if the header is not provided, `APIKeyHeader` will 

206 automatically cancel the request and send the client an error. 

207 

208 If `auto_error` is set to `False`, when the header is not available, 

209 instead of erroring out, the dependency result will be `None`. 

210 

211 This is useful when you want to have optional authentication. 

212 

213 It is also useful when you want to have authentication that can be 

214 provided in one of multiple optional ways (for example, in a header or 

215 in an HTTP Bearer token). 

216 """ 

217 ), 

218 ] = True, 

219 ): 

220 super().__init__( 1abcd

221 location=APIKeyIn.header, 

222 name=name, 

223 scheme_name=scheme_name, 

224 description=description, 

225 auto_error=auto_error, 

226 ) 

227 

228 async def __call__(self, request: Request) -> str | None: 1abcd

229 api_key = request.headers.get(self.model.name) 1IgJhKxRmSnTA0s1t2D

230 return self.check_api_key(api_key) 1IgJhKxRmSnTA0s1t2D

231 

232 

233class APIKeyCookie(APIKeyBase): 1abcd

234 """ 

235 API key authentication using a cookie. 

236 

237 This defines the name of the cookie that should be provided in the request with 

238 the API key and integrates that into the OpenAPI documentation. It extracts 

239 the key value sent in the cookie automatically and provides it as the dependency 

240 result. But it doesn't define how to set that cookie. 

241 

242 ## Usage 

243 

244 Create an instance object and use that object as the dependency in `Depends()`. 

245 

246 The dependency result will be a string containing the key value. 

247 

248 ## Example 

249 

250 ```python 

251 from fastapi import Depends, FastAPI 

252 from fastapi.security import APIKeyCookie 

253 

254 app = FastAPI() 

255 

256 cookie_scheme = APIKeyCookie(name="session") 

257 

258 

259 @app.get("/items/") 

260 async def read_items(session: str = Depends(cookie_scheme)): 

261 return {"session": session} 

262 ``` 

263 """ 

264 

265 def __init__( 1abcd

266 self, 

267 *, 

268 name: Annotated[str, Doc("Cookie name.")], 

269 scheme_name: Annotated[ 

270 str | None, 

271 Doc( 

272 """ 

273 Security scheme name. 

274 

275 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

276 """ 

277 ), 

278 ] = None, 

279 description: Annotated[ 

280 str | None, 

281 Doc( 

282 """ 

283 Security scheme description. 

284 

285 It will be included in the generated OpenAPI (e.g. visible at `/docs`). 

286 """ 

287 ), 

288 ] = None, 

289 auto_error: Annotated[ 

290 bool, 

291 Doc( 

292 """ 

293 By default, if the cookie is not provided, `APIKeyCookie` will 

294 automatically cancel the request and send the client an error. 

295 

296 If `auto_error` is set to `False`, when the cookie is not available, 

297 instead of erroring out, the dependency result will be `None`. 

298 

299 This is useful when you want to have optional authentication. 

300 

301 It is also useful when you want to have authentication that can be 

302 provided in one of multiple optional ways (for example, in a cookie or 

303 in an HTTP Bearer token). 

304 """ 

305 ), 

306 ] = True, 

307 ): 

308 super().__init__( 1abcd

309 location=APIKeyIn.cookie, 

310 name=name, 

311 scheme_name=scheme_name, 

312 description=description, 

313 auto_error=auto_error, 

314 ) 

315 

316 async def __call__(self, request: Request) -> str | None: 1abcd

317 api_key = request.cookies.get(self.model.name) 1FeGfHwOkPlQzXqYrZC

318 return self.check_api_key(api_key) 1FeGfHwOkPlQzXqYrZC