Coverage for fastapi/security/oauth2.py: 100%
64 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-09 09:16 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-09-09 09:16 +0000
1from typing import Any, Dict, List, Optional, Union, cast 1abcdef
3from fastapi.exceptions import HTTPException 1abcdef
4from fastapi.openapi.models import OAuth2 as OAuth2Model 1abcdef
5from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel 1abcdef
6from fastapi.param_functions import Form 1abcdef
7from fastapi.security.base import SecurityBase 1abcdef
8from fastapi.security.utils import get_authorization_scheme_param 1abcdef
9from starlette.requests import Request 1abcdef
10from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN 1abcdef
12# TODO: import from typing when deprecating Python 3.9
13from typing_extensions import Annotated, Doc 1abcdef
16class OAuth2PasswordRequestForm: 1abcdef
17 """
18 This is a dependency class to collect the `username` and `password` as form data
19 for an OAuth2 password flow.
21 The OAuth2 specification dictates that for a password flow the data should be
22 collected using form data (instead of JSON) and that it should have the specific
23 fields `username` and `password`.
25 All the initialization parameters are extracted from the request.
27 Read more about it in the
28 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
30 ## Example
32 ```python
33 from typing import Annotated
35 from fastapi import Depends, FastAPI
36 from fastapi.security import OAuth2PasswordRequestForm
38 app = FastAPI()
41 @app.post("/login")
42 def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
43 data = {}
44 data["scopes"] = []
45 for scope in form_data.scopes:
46 data["scopes"].append(scope)
47 if form_data.client_id:
48 data["client_id"] = form_data.client_id
49 if form_data.client_secret:
50 data["client_secret"] = form_data.client_secret
51 return data
52 ```
54 Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
55 You could have custom internal logic to separate it by colon characters (`:`) or
56 similar, and get the two parts `items` and `read`. Many applications do that to
57 group and organize permissions, you could do it as well in your application, just
58 know that that it is application specific, it's not part of the specification.
59 """
61 def __init__( 1abcdef
62 self,
63 *,
64 grant_type: Annotated[
65 Union[str, None],
66 Form(pattern="^password$"),
67 Doc(
68 """
69 The OAuth2 spec says it is required and MUST be the fixed string
70 "password". Nevertheless, this dependency class is permissive and
71 allows not passing it. If you want to enforce it, use instead the
72 `OAuth2PasswordRequestFormStrict` dependency.
73 """
74 ),
75 ] = None,
76 username: Annotated[
77 str,
78 Form(),
79 Doc(
80 """
81 `username` string. The OAuth2 spec requires the exact field name
82 `username`.
83 """
84 ),
85 ],
86 password: Annotated[
87 str,
88 Form(json_schema_extra={"format": "password"}),
89 Doc(
90 """
91 `password` string. The OAuth2 spec requires the exact field name
92 `password".
93 """
94 ),
95 ],
96 scope: Annotated[
97 str,
98 Form(),
99 Doc(
100 """
101 A single string with actually several scopes separated by spaces. Each
102 scope is also a string.
104 For example, a single string with:
106 ```python
107 "items:read items:write users:read profile openid"
108 ````
110 would represent the scopes:
112 * `items:read`
113 * `items:write`
114 * `users:read`
115 * `profile`
116 * `openid`
117 """
118 ),
119 ] = "",
120 client_id: Annotated[
121 Union[str, None],
122 Form(),
123 Doc(
124 """
125 If there's a `client_id`, it can be sent as part of the form fields.
126 But the OAuth2 specification recommends sending the `client_id` and
127 `client_secret` (if any) using HTTP Basic auth.
128 """
129 ),
130 ] = None,
131 client_secret: Annotated[
132 Union[str, None],
133 Form(json_schema_extra={"format": "password"}),
134 Doc(
135 """
136 If there's a `client_password` (and a `client_id`), they can be sent
137 as part of the form fields. But the OAuth2 specification recommends
138 sending the `client_id` and `client_secret` (if any) using HTTP Basic
139 auth.
140 """
141 ),
142 ] = None,
143 ):
144 self.grant_type = grant_type 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
145 self.username = username 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
146 self.password = password 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
147 self.scopes = scope.split() 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
148 self.client_id = client_id 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
149 self.client_secret = client_secret 2K L M 2 3 4 5 6 7 g h i j k N O P 8 9 ! # $ % l m n o p Q R S ' ( ) * + , q r s t u T U V - . / : ; = v w x y z W X Y ? @ [ ] ^ _ A B C D E Z 0 1 ` { | } ~ abF G H I J
152class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): 1abcdef
153 """
154 This is a dependency class to collect the `username` and `password` as form data
155 for an OAuth2 password flow.
157 The OAuth2 specification dictates that for a password flow the data should be
158 collected using form data (instead of JSON) and that it should have the specific
159 fields `username` and `password`.
161 All the initialization parameters are extracted from the request.
163 The only difference between `OAuth2PasswordRequestFormStrict` and
164 `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the
165 client to send the form field `grant_type` with the value `"password"`, which
166 is required in the OAuth2 specification (it seems that for no particular reason),
167 while for `OAuth2PasswordRequestForm` `grant_type` is optional.
169 Read more about it in the
170 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
172 ## Example
174 ```python
175 from typing import Annotated
177 from fastapi import Depends, FastAPI
178 from fastapi.security import OAuth2PasswordRequestForm
180 app = FastAPI()
183 @app.post("/login")
184 def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):
185 data = {}
186 data["scopes"] = []
187 for scope in form_data.scopes:
188 data["scopes"].append(scope)
189 if form_data.client_id:
190 data["client_id"] = form_data.client_id
191 if form_data.client_secret:
192 data["client_secret"] = form_data.client_secret
193 return data
194 ```
196 Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
197 You could have custom internal logic to separate it by colon characters (`:`) or
198 similar, and get the two parts `items` and `read`. Many applications do that to
199 group and organize permissions, you could do it as well in your application, just
200 know that that it is application specific, it's not part of the specification.
203 grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
204 This dependency is strict about it. If you want to be permissive, use instead the
205 OAuth2PasswordRequestForm dependency class.
206 username: username string. The OAuth2 spec requires the exact field name "username".
207 password: password string. The OAuth2 spec requires the exact field name "password".
208 scope: Optional string. Several scopes (each one a string) separated by spaces. E.g.
209 "items:read items:write users:read profile openid"
210 client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
211 using HTTP Basic auth, as: client_id:client_secret
212 client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
213 using HTTP Basic auth, as: client_id:client_secret
214 """
216 def __init__( 1abcdef
217 self,
218 grant_type: Annotated[
219 str,
220 Form(pattern="^password$"),
221 Doc(
222 """
223 The OAuth2 spec says it is required and MUST be the fixed string
224 "password". This dependency is strict about it. If you want to be
225 permissive, use instead the `OAuth2PasswordRequestForm` dependency
226 class.
227 """
228 ),
229 ],
230 username: Annotated[
231 str,
232 Form(),
233 Doc(
234 """
235 `username` string. The OAuth2 spec requires the exact field name
236 `username`.
237 """
238 ),
239 ],
240 password: Annotated[
241 str,
242 Form(),
243 Doc(
244 """
245 `password` string. The OAuth2 spec requires the exact field name
246 `password".
247 """
248 ),
249 ],
250 scope: Annotated[
251 str,
252 Form(),
253 Doc(
254 """
255 A single string with actually several scopes separated by spaces. Each
256 scope is also a string.
258 For example, a single string with:
260 ```python
261 "items:read items:write users:read profile openid"
262 ````
264 would represent the scopes:
266 * `items:read`
267 * `items:write`
268 * `users:read`
269 * `profile`
270 * `openid`
271 """
272 ),
273 ] = "",
274 client_id: Annotated[
275 Union[str, None],
276 Form(),
277 Doc(
278 """
279 If there's a `client_id`, it can be sent as part of the form fields.
280 But the OAuth2 specification recommends sending the `client_id` and
281 `client_secret` (if any) using HTTP Basic auth.
282 """
283 ),
284 ] = None,
285 client_secret: Annotated[
286 Union[str, None],
287 Form(),
288 Doc(
289 """
290 If there's a `client_password` (and a `client_id`), they can be sent
291 as part of the form fields. But the OAuth2 specification recommends
292 sending the `client_id` and `client_secret` (if any) using HTTP Basic
293 auth.
294 """
295 ),
296 ] = None,
297 ):
298 super().__init__( 1KLMNOPQRSTUVWXYZ01
299 grant_type=grant_type,
300 username=username,
301 password=password,
302 scope=scope,
303 client_id=client_id,
304 client_secret=client_secret,
305 )
308class OAuth2(SecurityBase): 1abcdef
309 """
310 This is the base class for OAuth2 authentication, an instance of it would be used
311 as a dependency. All other OAuth2 classes inherit from it and customize it for
312 each OAuth2 flow.
314 You normally would not create a new class inheriting from it but use one of the
315 existing subclasses, and maybe compose them if you want to support multiple flows.
317 Read more about it in the
318 [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/).
319 """
321 def __init__( 1abcdef
322 self,
323 *,
324 flows: Annotated[
325 Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]],
326 Doc(
327 """
328 The dictionary of OAuth2 flows.
329 """
330 ),
331 ] = OAuthFlowsModel(),
332 scheme_name: Annotated[
333 Optional[str],
334 Doc(
335 """
336 Security scheme name.
338 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
339 """
340 ),
341 ] = None,
342 description: Annotated[
343 Optional[str],
344 Doc(
345 """
346 Security scheme description.
348 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
349 """
350 ),
351 ] = None,
352 auto_error: Annotated[
353 bool,
354 Doc(
355 """
356 By default, if no HTTP Authorization header is provided, required for
357 OAuth2 authentication, it will automatically cancel the request and
358 send the client an error.
360 If `auto_error` is set to `False`, when the HTTP Authorization header
361 is not available, instead of erroring out, the dependency result will
362 be `None`.
364 This is useful when you want to have optional authentication.
366 It is also useful when you want to have authentication that can be
367 provided in one of multiple optional ways (for example, with OAuth2
368 or in a cookie).
369 """
370 ),
371 ] = True,
372 ):
373 self.model = OAuth2Model( 1abcdef
374 flows=cast(OAuthFlowsModel, flows), description=description
375 )
376 self.scheme_name = scheme_name or self.__class__.__name__ 1abcdef
377 self.auto_error = auto_error 1abcdef
379 async def __call__(self, request: Request) -> Optional[str]: 1abcdef
380 authorization = request.headers.get("Authorization") 2ddAcedfdBcgdhdCcidjdDckdldEcmdndFcodpdGcqdrdHcsdtdIcudvdJcwdxdKcydzdLcAdBdMcCdDdNcEdFdOcGdHdPcIdJdQcKdLdRcMd
381 if not authorization: 2ddAcedfdBcgdhdCcidjdDckdldEcmdndFcodpdGcqdrdHcsdtdIcudvdJcwdxdKcydzdLcAdBdMcCdDdNcEdFdOcGdHdPcIdJdQcKdLdRcMd
382 if self.auto_error: 2AcBcCcDcEcFcGcHcIcJcKcLcMcNcOcPcQcRc
383 raise HTTPException( 2AcDcGcJcMcPc
384 status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
385 )
386 else:
387 return None 2BcCcEcFcHcIcKcLcNcOcQcRc
388 return authorization 2ddedfdgdhdidjdkdldmdndodpdqdrdsdtdudvdwdxdydzdAdBdCdDdEdFdGdHdIdJdKdLdMd
391class OAuth2PasswordBearer(OAuth2): 1abcdef
392 """
393 OAuth2 flow for authentication using a bearer token obtained with a password.
394 An instance of it would be used as a dependency.
396 Read more about it in the
397 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
398 """
400 def __init__( 1abcdef
401 self,
402 tokenUrl: Annotated[
403 str,
404 Doc(
405 """
406 The URL to obtain the OAuth2 token. This would be the *path operation*
407 that has `OAuth2PasswordRequestForm` as a dependency.
408 """
409 ),
410 ],
411 scheme_name: Annotated[
412 Optional[str],
413 Doc(
414 """
415 Security scheme name.
417 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
418 """
419 ),
420 ] = None,
421 scopes: Annotated[
422 Optional[Dict[str, str]],
423 Doc(
424 """
425 The OAuth2 scopes that would be required by the *path operations* that
426 use this dependency.
427 """
428 ),
429 ] = None,
430 description: Annotated[
431 Optional[str],
432 Doc(
433 """
434 Security scheme description.
436 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
437 """
438 ),
439 ] = None,
440 auto_error: Annotated[
441 bool,
442 Doc(
443 """
444 By default, if no HTTP Authorization header is provided, required for
445 OAuth2 authentication, it will automatically cancel the request and
446 send the client an error.
448 If `auto_error` is set to `False`, when the HTTP Authorization header
449 is not available, instead of erroring out, the dependency result will
450 be `None`.
452 This is useful when you want to have optional authentication.
454 It is also useful when you want to have authentication that can be
455 provided in one of multiple optional ways (for example, with OAuth2
456 or in a cookie).
457 """
458 ),
459 ] = True,
460 refreshUrl: Annotated[
461 Optional[str],
462 Doc(
463 """
464 The URL to refresh the token and obtain a new one.
465 """
466 ),
467 ] = None,
468 ):
469 if not scopes: 1abcdef
470 scopes = {} 1abcdef
471 flows = OAuthFlowsModel( 1abcdef
472 password=cast(
473 Any,
474 {
475 "tokenUrl": tokenUrl,
476 "refreshUrl": refreshUrl,
477 "scopes": scopes,
478 },
479 )
480 )
481 super().__init__( 1abcdef
482 flows=flows,
483 scheme_name=scheme_name,
484 description=description,
485 auto_error=auto_error,
486 )
488 async def __call__(self, request: Request) -> Optional[str]: 1abcdef
489 authorization = request.headers.get("Authorization") 2zbAbScBbCbTcDbEbUcVcWcFbGbXcbbHbIbg h Jbi j k cbdbebKbLbYcMbNbZcObPb0c1c2cQbRb3cfbSbTbl m Ubn o p gbhbibVbWb4cXbYb5cZb0b6c7c8c1b2b9cjb3b4bq r 5bs t u kblbmb6b7b!c8b9b#c!b#b$c%c'c$b%b(cnb'b(bv w )bx y z obpbqb*b+b)c,b-b*c.b/b+c,c-c:b;b.crb=b?bA B @bC D E sbtbub[b]b/c^b_b:c`b{b;c=c?c|b}b@cvb~bacF G bcH I J wbxbyb
490 scheme, param = get_authorization_scheme_param(authorization) 2zbAbScBbCbTcDbEbUcVcWcFbGbXcbbHbIbg h Jbi j k cbdbebKbLbYcMbNbZcObPb0c1c2cQbRb3cfbSbTbl m Ubn o p gbhbibVbWb4cXbYb5cZb0b6c7c8c1b2b9cjb3b4bq r 5bs t u kblbmb6b7b!c8b9b#c!b#b$c%c'c$b%b(cnb'b(bv w )bx y z obpbqb*b+b)c,b-b*c.b/b+c,c-c:b;b.crb=b?bA B @bC D E sbtbub[b]b/c^b_b:c`b{b;c=c?c|b}b@cvb~bacF G bcH I J wbxbyb
491 if not authorization or scheme.lower() != "bearer": 2zbAbScBbCbTcDbEbUcVcWcFbGbXcbbHbIbg h Jbi j k cbdbebKbLbYcMbNbZcObPb0c1c2cQbRb3cfbSbTbl m Ubn o p gbhbibVbWb4cXbYb5cZb0b6c7c8c1b2b9cjb3b4bq r 5bs t u kblbmb6b7b!c8b9b#c!b#b$c%c'c$b%b(cnb'b(bv w )bx y z obpbqb*b+b)c,b-b*c.b/b+c,c-c:b;b.crb=b?bA B @bC D E sbtbub[b]b/c^b_b:c`b{b;c=c?c|b}b@cvb~bacF G bcH I J wbxbyb
492 if self.auto_error: 2zbAbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*b+b,b-b.b/b:b;b=b?b@b[b]b^b_b`b{b|b}b~bacbc
493 raise HTTPException( 2DbEbFbGbHbIbJbObPbQbRbSbTbUbZb0b1b2b3b4b5b!b#b$b%b'b(b)b.b/b:b;b=b?b@b`b{b|b}b~bacbc
494 status_code=HTTP_401_UNAUTHORIZED,
495 detail="Not authenticated",
496 headers={"WWW-Authenticate": "Bearer"},
497 )
498 else:
499 return None 2zbAbBbCbKbLbMbNbVbWbXbYb6b7b8b9b*b+b,b-b[b]b^b_b
500 return param 2ScTcUcVcWcXcbbg h i j k cbdbebYcZc0c1c2c3cfbl m n o p gbhbib4c5c6c7c8c9cjbq r s t u kblbmb!c#c$c%c'c(cnbv w x y z obpbqb)c*c+c,c-c.crbA B C D E sbtbub/c:c;c=c?c@cvbF G H I J wbxbyb
503class OAuth2AuthorizationCodeBearer(OAuth2): 1abcdef
504 """
505 OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code
506 flow. An instance of it would be used as a dependency.
507 """
509 def __init__( 1abcdef
510 self,
511 authorizationUrl: str,
512 tokenUrl: Annotated[
513 str,
514 Doc(
515 """
516 The URL to obtain the OAuth2 token.
517 """
518 ),
519 ],
520 refreshUrl: Annotated[
521 Optional[str],
522 Doc(
523 """
524 The URL to refresh the token and obtain a new one.
525 """
526 ),
527 ] = None,
528 scheme_name: Annotated[
529 Optional[str],
530 Doc(
531 """
532 Security scheme name.
534 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
535 """
536 ),
537 ] = None,
538 scopes: Annotated[
539 Optional[Dict[str, str]],
540 Doc(
541 """
542 The OAuth2 scopes that would be required by the *path operations* that
543 use this dependency.
544 """
545 ),
546 ] = None,
547 description: Annotated[
548 Optional[str],
549 Doc(
550 """
551 Security scheme description.
553 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
554 """
555 ),
556 ] = None,
557 auto_error: Annotated[
558 bool,
559 Doc(
560 """
561 By default, if no HTTP Authorization header is provided, required for
562 OAuth2 authentication, it will automatically cancel the request and
563 send the client an error.
565 If `auto_error` is set to `False`, when the HTTP Authorization header
566 is not available, instead of erroring out, the dependency result will
567 be `None`.
569 This is useful when you want to have optional authentication.
571 It is also useful when you want to have authentication that can be
572 provided in one of multiple optional ways (for example, with OAuth2
573 or in a cookie).
574 """
575 ),
576 ] = True,
577 ):
578 if not scopes: 1abcdef
579 scopes = {} 1abcdef
580 flows = OAuthFlowsModel( 1abcdef
581 authorizationCode=cast(
582 Any,
583 {
584 "authorizationUrl": authorizationUrl,
585 "tokenUrl": tokenUrl,
586 "refreshUrl": refreshUrl,
587 "scopes": scopes,
588 },
589 )
590 )
591 super().__init__( 1abcdef
592 flows=flows,
593 scheme_name=scheme_name,
594 description=description,
595 auto_error=auto_error,
596 )
598 async def __call__(self, request: Request) -> Optional[str]: 1abcdef
599 authorization = request.headers.get("Authorization") 2ccdc[cecfc]cgchc^cicjc_ckclc`cmcnc{cocpc|cqcrc}csctc~cucvcadwcxcbdyczccd
600 scheme, param = get_authorization_scheme_param(authorization) 2ccdc[cecfc]cgchc^cicjc_ckclc`cmcnc{cocpc|cqcrc}csctc~cucvcadwcxcbdyczccd
601 if not authorization or scheme.lower() != "bearer": 2ccdc[cecfc]cgchc^cicjc_ckclc`cmcnc{cocpc|cqcrc}csctc~cucvcadwcxcbdyczccd
602 if self.auto_error: 2ccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczc
603 raise HTTPException( 2ccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczc
604 status_code=HTTP_401_UNAUTHORIZED,
605 detail="Not authenticated",
606 headers={"WWW-Authenticate": "Bearer"},
607 )
608 else:
609 return None # pragma: nocover
610 return param 2[c]c^c_c`c{c|c}c~cadbdcd
613class SecurityScopes: 1abcdef
614 """
615 This is a special class that you can define in a parameter in a dependency to
616 obtain the OAuth2 scopes required by all the dependencies in the same chain.
618 This way, multiple dependencies can have different scopes, even when used in the
619 same *path operation*. And with this, you can access all the scopes required in
620 all those dependencies in a single place.
622 Read more about it in the
623 [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
624 """
626 def __init__( 1abcdef
627 self,
628 scopes: Annotated[
629 Optional[List[str]],
630 Doc(
631 """
632 This will be filled by FastAPI.
633 """
634 ),
635 ] = None,
636 ):
637 self.scopes: Annotated[ 2NdOdPdbbg h i j k cbdbebQdRdSdfbl m n o p gbhbibTdUdVdjbq r s t u kblbmbWdXdYdnbv w x y z obpbqbZd0d1drbA B C D E sbtbub2d3d4dvbF G H I J wbxbyb
638 List[str],
639 Doc(
640 """
641 The list of all the scopes required by dependencies.
642 """
643 ),
644 ] = scopes or []
645 self.scope_str: Annotated[ 2NdOdPdbbg h i j k cbdbebQdRdSdfbl m n o p gbhbibTdUdVdjbq r s t u kblbmbWdXdYdnbv w x y z obpbqbZd0d1drbA B C D E sbtbub2d3d4dvbF G H I J wbxbyb
646 str,
647 Doc(
648 """
649 All the scopes required by all the dependencies in a single string
650 separated by spaces, as defined in the OAuth2 specification.
651 """
652 ),
653 ] = " ".join(self.scopes)