Coverage for fastapi / security / oauth2.py: 100%
66 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1from typing import Annotated, Any, cast 1abcd
3from annotated_doc import Doc 1abcd
4from fastapi.exceptions import HTTPException 1abcd
5from fastapi.openapi.models import OAuth2 as OAuth2Model 1abcd
6from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel 1abcd
7from fastapi.param_functions import Form 1abcd
8from fastapi.security.base import SecurityBase 1abcd
9from fastapi.security.utils import get_authorization_scheme_param 1abcd
10from starlette.requests import Request 1abcd
11from starlette.status import HTTP_401_UNAUTHORIZED 1abcd
14class OAuth2PasswordRequestForm: 1abcd
15 """
16 This is a dependency class to collect the `username` and `password` as form data
17 for an OAuth2 password flow.
19 The OAuth2 specification dictates that for a password flow the data should be
20 collected using form data (instead of JSON) and that it should have the specific
21 fields `username` and `password`.
23 All the initialization parameters are extracted from the request.
25 Read more about it in the
26 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
28 ## Example
30 ```python
31 from typing import Annotated
33 from fastapi import Depends, FastAPI
34 from fastapi.security import OAuth2PasswordRequestForm
36 app = FastAPI()
39 @app.post("/login")
40 def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
41 data = {}
42 data["scopes"] = []
43 for scope in form_data.scopes:
44 data["scopes"].append(scope)
45 if form_data.client_id:
46 data["client_id"] = form_data.client_id
47 if form_data.client_secret:
48 data["client_secret"] = form_data.client_secret
49 return data
50 ```
52 Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
53 You could have custom internal logic to separate it by colon characters (`:`) or
54 similar, and get the two parts `items` and `read`. Many applications do that to
55 group and organize permissions, you could do it as well in your application, just
56 know that that it is application specific, it's not part of the specification.
57 """
59 def __init__( 1abcd
60 self,
61 *,
62 grant_type: Annotated[
63 str | None,
64 Form(pattern="^password$"),
65 Doc(
66 """
67 The OAuth2 spec says it is required and MUST be the fixed string
68 "password". Nevertheless, this dependency class is permissive and
69 allows not passing it. If you want to enforce it, use instead the
70 `OAuth2PasswordRequestFormStrict` dependency.
72 Read more about it in the
73 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
74 """
75 ),
76 ] = None,
77 username: Annotated[
78 str,
79 Form(),
80 Doc(
81 """
82 `username` string. The OAuth2 spec requires the exact field name
83 `username`.
85 Read more about it in the
86 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
87 """
88 ),
89 ],
90 password: Annotated[
91 str,
92 Form(json_schema_extra={"format": "password"}),
93 Doc(
94 """
95 `password` string. The OAuth2 spec requires the exact field name
96 `password`.
98 Read more about it in the
99 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
100 """
101 ),
102 ],
103 scope: Annotated[
104 str,
105 Form(),
106 Doc(
107 """
108 A single string with actually several scopes separated by spaces. Each
109 scope is also a string.
111 For example, a single string with:
113 ```python
114 "items:read items:write users:read profile openid"
115 ````
117 would represent the scopes:
119 * `items:read`
120 * `items:write`
121 * `users:read`
122 * `profile`
123 * `openid`
125 Read more about it in the
126 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
127 """
128 ),
129 ] = "",
130 client_id: Annotated[
131 str | None,
132 Form(),
133 Doc(
134 """
135 If there's a `client_id`, it can be sent as part of the form fields.
136 But the OAuth2 specification recommends sending the `client_id` and
137 `client_secret` (if any) using HTTP Basic auth.
138 """
139 ),
140 ] = None,
141 client_secret: Annotated[
142 str | None,
143 Form(json_schema_extra={"format": "password"}),
144 Doc(
145 """
146 If there's a `client_password` (and a `client_id`), they can be sent
147 as part of the form fields. But the OAuth2 specification recommends
148 sending the `client_id` and `client_secret` (if any) using HTTP Basic
149 auth.
150 """
151 ),
152 ] = None,
153 ):
154 self.grant_type = grant_type 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
155 self.username = username 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
156 self.password = password 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
157 self.scopes = scope.split() 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
158 self.client_id = client_id 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
159 self.client_secret = client_secret 1CDELMNOPQtuvRSTefghiFGHUVWXYZwxy012jklmnIJK345678zAB9!#opqrs
162class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): 1abcd
163 """
164 This is a dependency class to collect the `username` and `password` as form data
165 for an OAuth2 password flow.
167 The OAuth2 specification dictates that for a password flow the data should be
168 collected using form data (instead of JSON) and that it should have the specific
169 fields `username` and `password`.
171 All the initialization parameters are extracted from the request.
173 The only difference between `OAuth2PasswordRequestFormStrict` and
174 `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the
175 client to send the form field `grant_type` with the value `"password"`, which
176 is required in the OAuth2 specification (it seems that for no particular reason),
177 while for `OAuth2PasswordRequestForm` `grant_type` is optional.
179 Read more about it in the
180 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
182 ## Example
184 ```python
185 from typing import Annotated
187 from fastapi import Depends, FastAPI
188 from fastapi.security import OAuth2PasswordRequestForm
190 app = FastAPI()
193 @app.post("/login")
194 def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):
195 data = {}
196 data["scopes"] = []
197 for scope in form_data.scopes:
198 data["scopes"].append(scope)
199 if form_data.client_id:
200 data["client_id"] = form_data.client_id
201 if form_data.client_secret:
202 data["client_secret"] = form_data.client_secret
203 return data
204 ```
206 Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
207 You could have custom internal logic to separate it by colon characters (`:`) or
208 similar, and get the two parts `items` and `read`. Many applications do that to
209 group and organize permissions, you could do it as well in your application, just
210 know that that it is application specific, it's not part of the specification.
213 grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password".
214 This dependency is strict about it. If you want to be permissive, use instead the
215 OAuth2PasswordRequestForm dependency class.
216 username: username string. The OAuth2 spec requires the exact field name "username".
217 password: password string. The OAuth2 spec requires the exact field name "password".
218 scope: Optional string. Several scopes (each one a string) separated by spaces. E.g.
219 "items:read items:write users:read profile openid"
220 client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
221 using HTTP Basic auth, as: client_id:client_secret
222 client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)
223 using HTTP Basic auth, as: client_id:client_secret
224 """
226 def __init__( 1abcd
227 self,
228 grant_type: Annotated[
229 str,
230 Form(pattern="^password$"),
231 Doc(
232 """
233 The OAuth2 spec says it is required and MUST be the fixed string
234 "password". This dependency is strict about it. If you want to be
235 permissive, use instead the `OAuth2PasswordRequestForm` dependency
236 class.
238 Read more about it in the
239 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
240 """
241 ),
242 ],
243 username: Annotated[
244 str,
245 Form(),
246 Doc(
247 """
248 `username` string. The OAuth2 spec requires the exact field name
249 `username`.
251 Read more about it in the
252 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
253 """
254 ),
255 ],
256 password: Annotated[
257 str,
258 Form(),
259 Doc(
260 """
261 `password` string. The OAuth2 spec requires the exact field name
262 `password`.
264 Read more about it in the
265 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
266 """
267 ),
268 ],
269 scope: Annotated[
270 str,
271 Form(),
272 Doc(
273 """
274 A single string with actually several scopes separated by spaces. Each
275 scope is also a string.
277 For example, a single string with:
279 ```python
280 "items:read items:write users:read profile openid"
281 ````
283 would represent the scopes:
285 * `items:read`
286 * `items:write`
287 * `users:read`
288 * `profile`
289 * `openid`
291 Read more about it in the
292 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
293 """
294 ),
295 ] = "",
296 client_id: Annotated[
297 str | None,
298 Form(),
299 Doc(
300 """
301 If there's a `client_id`, it can be sent as part of the form fields.
302 But the OAuth2 specification recommends sending the `client_id` and
303 `client_secret` (if any) using HTTP Basic auth.
304 """
305 ),
306 ] = None,
307 client_secret: Annotated[
308 str | None,
309 Form(),
310 Doc(
311 """
312 If there's a `client_password` (and a `client_id`), they can be sent
313 as part of the form fields. But the OAuth2 specification recommends
314 sending the `client_id` and `client_secret` (if any) using HTTP Basic
315 auth.
316 """
317 ),
318 ] = None,
319 ):
320 super().__init__( 1CDEFGHIJK
321 grant_type=grant_type,
322 username=username,
323 password=password,
324 scope=scope,
325 client_id=client_id,
326 client_secret=client_secret,
327 )
330class OAuth2(SecurityBase): 1abcd
331 """
332 This is the base class for OAuth2 authentication, an instance of it would be used
333 as a dependency. All other OAuth2 classes inherit from it and customize it for
334 each OAuth2 flow.
336 You normally would not create a new class inheriting from it but use one of the
337 existing subclasses, and maybe compose them if you want to support multiple flows.
339 Read more about it in the
340 [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/).
341 """
343 def __init__( 1abcd
344 self,
345 *,
346 flows: Annotated[
347 OAuthFlowsModel | dict[str, dict[str, Any]],
348 Doc(
349 """
350 The dictionary of OAuth2 flows.
351 """
352 ),
353 ] = OAuthFlowsModel(),
354 scheme_name: Annotated[
355 str | None,
356 Doc(
357 """
358 Security scheme name.
360 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
361 """
362 ),
363 ] = None,
364 description: Annotated[
365 str | None,
366 Doc(
367 """
368 Security scheme description.
370 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
371 """
372 ),
373 ] = None,
374 auto_error: Annotated[
375 bool,
376 Doc(
377 """
378 By default, if no HTTP Authorization header is provided, required for
379 OAuth2 authentication, it will automatically cancel the request and
380 send the client an error.
382 If `auto_error` is set to `False`, when the HTTP Authorization header
383 is not available, instead of erroring out, the dependency result will
384 be `None`.
386 This is useful when you want to have optional authentication.
388 It is also useful when you want to have authentication that can be
389 provided in one of multiple optional ways (for example, with OAuth2
390 or in a cookie).
391 """
392 ),
393 ] = True,
394 ):
395 self.model = OAuth2Model( 1abcd
396 flows=cast(OAuthFlowsModel, flows), description=description
397 )
398 self.scheme_name = scheme_name or self.__class__.__name__ 1abcd
399 self.auto_error = auto_error 1abcd
401 def make_not_authenticated_error(self) -> HTTPException: 1abcd
402 """
403 The OAuth 2 specification doesn't define the challenge that should be used,
404 because a `Bearer` token is not really the only option to authenticate.
406 But declaring any other authentication challenge would be application-specific
407 as it's not defined in the specification.
409 For practical reasons, this method uses the `Bearer` challenge by default, as
410 it's probably the most common one.
412 If you are implementing an OAuth2 authentication scheme other than the provided
413 ones in FastAPI (based on bearer tokens), you might want to override this.
415 Ref: https://datatracker.ietf.org/doc/html/rfc6749
416 """
417 return HTTPException( 2Mb$ % ' ( ) * + , - . / : ; = Nb? @ [ ] ^ _ ` { | } ~ abbbcbObdbebfbgbhbibjbkblbmbnbobpbqb
418 status_code=HTTP_401_UNAUTHORIZED,
419 detail="Not authenticated",
420 headers={"WWW-Authenticate": "Bearer"},
421 )
423 async def __call__(self, request: Request) -> str | None: 1abcd
424 authorization = request.headers.get("Authorization") 2EcMbFcGc1bHcIc2bJcKcNbLcMc3bNcOc4bPcQcObRcSc5bTcUc6bVc
425 if not authorization: 2EcMbFcGc1bHcIc2bJcKcNbLcMc3bNcOc4bPcQcObRcSc5bTcUc6bVc
426 if self.auto_error: 2Mb1b2bNb3b4bOb5b6b
427 raise self.make_not_authenticated_error() 2MbNbOb
428 else:
429 return None 21b2b3b4b5b6b
430 return authorization 2EcFcGcHcIcJcKcLcMcNcOcPcQcRcScTcUcVc
433class OAuth2PasswordBearer(OAuth2): 1abcd
434 """
435 OAuth2 flow for authentication using a bearer token obtained with a password.
436 An instance of it would be used as a dependency.
438 Read more about it in the
439 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
440 """
442 def __init__( 1abcd
443 self,
444 tokenUrl: Annotated[
445 str,
446 Doc(
447 """
448 The URL to obtain the OAuth2 token. This would be the *path operation*
449 that has `OAuth2PasswordRequestForm` as a dependency.
451 Read more about it in the
452 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
453 """
454 ),
455 ],
456 scheme_name: Annotated[
457 str | None,
458 Doc(
459 """
460 Security scheme name.
462 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
463 """
464 ),
465 ] = None,
466 scopes: Annotated[
467 dict[str, str] | None,
468 Doc(
469 """
470 The OAuth2 scopes that would be required by the *path operations* that
471 use this dependency.
473 Read more about it in the
474 [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).
475 """
476 ),
477 ] = None,
478 description: Annotated[
479 str | None,
480 Doc(
481 """
482 Security scheme description.
484 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
485 """
486 ),
487 ] = None,
488 auto_error: Annotated[
489 bool,
490 Doc(
491 """
492 By default, if no HTTP Authorization header is provided, required for
493 OAuth2 authentication, it will automatically cancel the request and
494 send the client an error.
496 If `auto_error` is set to `False`, when the HTTP Authorization header
497 is not available, instead of erroring out, the dependency result will
498 be `None`.
500 This is useful when you want to have optional authentication.
502 It is also useful when you want to have authentication that can be
503 provided in one of multiple optional ways (for example, with OAuth2
504 or in a cookie).
505 """
506 ),
507 ] = True,
508 refreshUrl: Annotated[
509 str | None,
510 Doc(
511 """
512 The URL to refresh the token and obtain a new one.
513 """
514 ),
515 ] = None,
516 ):
517 if not scopes: 1abcd
518 scopes = {} 1abcd
519 flows = OAuthFlowsModel( 1abcd
520 password=cast(
521 Any,
522 {
523 "tokenUrl": tokenUrl,
524 "refreshUrl": refreshUrl,
525 "scopes": scopes,
526 },
527 )
528 )
529 super().__init__( 1abcd
530 flows=flows,
531 scheme_name=scheme_name,
532 description=description,
533 auto_error=auto_error,
534 )
536 async def __call__(self, request: Request) -> str | None: 1abcd
537 authorization = request.headers.get("Authorization") 2rbsbtbPbQb7bRbSb8b) * 9b+ !b#b$b, - %b'b. / t u v (b)b*bub: ; e f = g h i vbwbxbybzbAbTbUb+bVbWb,b^ _ -b` .b/b:b{ | ;b=b} ~ w x y ?b@b[bBbabbbj k cbl m n CbDbEbFbGbHbXbYb]bZb0b^bhbib_bjb`b{b|bkblb}b~bmbnbz A B acbcccIbobpbo p qbq r s JbKbLb
538 scheme, param = get_authorization_scheme_param(authorization) 2rbsbtbPbQb7bRbSb8b) * 9b+ !b#b$b, - %b'b. / t u v (b)b*bub: ; e f = g h i vbwbxbybzbAbTbUb+bVbWb,b^ _ -b` .b/b:b{ | ;b=b} ~ w x y ?b@b[bBbabbbj k cbl m n CbDbEbFbGbHbXbYb]bZb0b^bhbib_bjb`b{b|bkblb}b~bmbnbz A B acbcccIbobpbo p qbq r s JbKbLb
539 if not authorization or scheme.lower() != "bearer": 2rbsbtbPbQb7bRbSb8b) * 9b+ !b#b$b, - %b'b. / t u v (b)b*bub: ; e f = g h i vbwbxbybzbAbTbUb+bVbWb,b^ _ -b` .b/b:b{ | ;b=b} ~ w x y ?b@b[bBbabbbj k cbl m n CbDbEbFbGbHbXbYb]bZb0b^bhbib_bjb`b{b|bkblb}b~bmbnbz A B acbcccIbobpbo p qbq r s JbKbLb
540 if self.auto_error: 2PbQbRbSb) * + , - . / : ; = TbUbVbWb^ _ ` { | } ~ abbbcbXbYbZb0bhbibjbkblbmbnbobpbqb
541 raise self.make_not_authenticated_error() 2) * + , - . / : ; = ^ _ ` { | } ~ abbbcbhbibjbkblbmbnbobpbqb
542 else:
543 return None 2PbQbRbSbTbUbVbWbXbYbZb0b
544 return param 2rbsbtb7b8b9b!b#b$b%b'bt u v (b)b*bube f g h i vbwbxbybzbAb+b,b-b.b/b:b;b=bw x y ?b@b[bBbj k l m n CbDbEbFbGbHb]b^b_b`b{b|b}b~bz A B acbcccIbo p q r s JbKbLb
547class OAuth2AuthorizationCodeBearer(OAuth2): 1abcd
548 """
549 OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code
550 flow. An instance of it would be used as a dependency.
551 """
553 def __init__( 1abcd
554 self,
555 authorizationUrl: str,
556 tokenUrl: Annotated[
557 str,
558 Doc(
559 """
560 The URL to obtain the OAuth2 token.
561 """
562 ),
563 ],
564 refreshUrl: Annotated[
565 str | None,
566 Doc(
567 """
568 The URL to refresh the token and obtain a new one.
569 """
570 ),
571 ] = None,
572 scheme_name: Annotated[
573 str | None,
574 Doc(
575 """
576 Security scheme name.
578 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
579 """
580 ),
581 ] = None,
582 scopes: Annotated[
583 dict[str, str] | None,
584 Doc(
585 """
586 The OAuth2 scopes that would be required by the *path operations* that
587 use this dependency.
588 """
589 ),
590 ] = None,
591 description: Annotated[
592 str | None,
593 Doc(
594 """
595 Security scheme description.
597 It will be included in the generated OpenAPI (e.g. visible at `/docs`).
598 """
599 ),
600 ] = None,
601 auto_error: Annotated[
602 bool,
603 Doc(
604 """
605 By default, if no HTTP Authorization header is provided, required for
606 OAuth2 authentication, it will automatically cancel the request and
607 send the client an error.
609 If `auto_error` is set to `False`, when the HTTP Authorization header
610 is not available, instead of erroring out, the dependency result will
611 be `None`.
613 This is useful when you want to have optional authentication.
615 It is also useful when you want to have authentication that can be
616 provided in one of multiple optional ways (for example, with OAuth2
617 or in a cookie).
618 """
619 ),
620 ] = True,
621 ):
622 if not scopes: 1abcd
623 scopes = {} 1abcd
624 flows = OAuthFlowsModel( 1abcd
625 authorizationCode=cast(
626 Any,
627 {
628 "authorizationUrl": authorizationUrl,
629 "tokenUrl": tokenUrl,
630 "refreshUrl": refreshUrl,
631 "scopes": scopes,
632 },
633 )
634 )
635 super().__init__( 1abcd
636 flows=flows,
637 scheme_name=scheme_name,
638 description=description,
639 auto_error=auto_error,
640 )
642 async def __call__(self, request: Request) -> str | None: 1abcd
643 authorization = request.headers.get("Authorization") 2$ % dcec' ( fcgchcicjckclc? @ mcnc[ ] ocpcqcrcsctcucdbebvcwcfbgbxcyczcAcBcCcDc
644 scheme, param = get_authorization_scheme_param(authorization) 2$ % dcec' ( fcgchcicjckclc? @ mcnc[ ] ocpcqcrcsctcucdbebvcwcfbgbxcyczcAcBcCcDc
645 if not authorization or scheme.lower() != "bearer": 2$ % dcec' ( fcgchcicjckclc? @ mcnc[ ] ocpcqcrcsctcucdbebvcwcfbgbxcyczcAcBcCcDc
646 if self.auto_error: 2$ % ' ( ? @ [ ] dbebfbgb
647 raise self.make_not_authenticated_error() 2$ % ' ( ? @ [ ] dbebfbgb
648 else:
649 return None # pragma: nocover
650 return param 2dcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczcAcBcCcDc
653class SecurityScopes: 1abcd
654 """
655 This is a special class that you can define in a parameter in a dependency to
656 obtain the OAuth2 scopes required by all the dependencies in the same chain.
658 This way, multiple dependencies can have different scopes, even when used in the
659 same *path operation*. And with this, you can access all the scopes required in
660 all those dependencies in a single place.
662 Read more about it in the
663 [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).
664 """
666 def __init__( 1abcd
667 self,
668 scopes: Annotated[
669 list[str] | None,
670 Doc(
671 """
672 This will be filled by FastAPI.
673 """
674 ),
675 ] = None,
676 ):
677 self.scopes: Annotated[ 2rbsbtbWcXcYcZc0cube f g h i vbwbxbybzbAb1c2c3c4c5cBbj k l m n CbDbEbFbGbHb6c7c8c9c!cIbo p q r s JbKbLb
678 list[str],
679 Doc(
680 """
681 The list of all the scopes required by dependencies.
682 """
683 ),
684 ] = scopes or []
685 self.scope_str: Annotated[ 2rbsbtbWcXcYcZc0cube f g h i vbwbxbybzbAb1c2c3c4c5cBbj k l m n CbDbEbFbGbHb6c7c8c9c!cIbo p q r s JbKbLb
686 str,
687 Doc(
688 """
689 All the scopes required by all the dependencies in a single string
690 separated by spaces, as defined in the OAuth2 specification.
691 """
692 ),
693 ] = " ".join(self.scopes)