fastapi/security/oauth2.py PYTHON 694 lines View on github.com → Search inside
1from typing import Annotated, Any, cast23from annotated_doc import Doc4from fastapi.exceptions import HTTPException5from fastapi.openapi.models import OAuth2 as OAuth2Model6from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel7from fastapi.param_functions import Form8from fastapi.security.base import SecurityBase9from fastapi.security.utils import get_authorization_scheme_param10from starlette.requests import Request11from starlette.status import HTTP_401_UNAUTHORIZED121314class OAuth2PasswordRequestForm:15    """16    This is a dependency class to collect the `username` and `password` as form data17    for an OAuth2 password flow.1819    The OAuth2 specification dictates that for a password flow the data should be20    collected using form data (instead of JSON) and that it should have the specific21    fields `username` and `password`.2223    All the initialization parameters are extracted from the request.2425    Read more about it in the26    [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).2728    ## Example2930    ```python31    from typing import Annotated3233    from fastapi import Depends, FastAPI34    from fastapi.security import OAuth2PasswordRequestForm3536    app = FastAPI()373839    @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_id47        if form_data.client_secret:48            data["client_secret"] = form_data.client_secret49        return data50    ```5152    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 (`:`) or54    similar, and get the two parts `items` and `read`. Many applications do that to55    group and organize permissions, you could do it as well in your application, just56    know that it is application specific, it's not part of the specification.57    """5859    def __init__(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 string68                "password". Nevertheless, this dependency class is permissive and69                allows not passing it. If you want to enforce it, use instead the70                `OAuth2PasswordRequestFormStrict` dependency.7172                Read more about it in the73                [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 name83                `username`.8485                Read more about it in the86                [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 name96                `password`.9798                Read more about it in the99                [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. Each109                scope is also a string.110111                For example, a single string with:112113                ```python114                "items:read items:write users:read profile openid"115                ````116117                would represent the scopes:118119                * `items:read`120                * `items:write`121                * `users:read`122                * `profile`123                * `openid`124125                Read more about it in the126                [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` and137                `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_secret` (and a `client_id`), they can be sent147                as part of the form fields. But the OAuth2 specification recommends148                sending the `client_id` and `client_secret` (if any) using HTTP Basic149                auth.150                """151            ),152        ] = None,153    ):154        self.grant_type = grant_type155        self.username = username156        self.password = password157        self.scopes = scope.split()158        self.client_id = client_id159        self.client_secret = client_secret160161162class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):163    """164    This is a dependency class to collect the `username` and `password` as form data165    for an OAuth2 password flow.166167    The OAuth2 specification dictates that for a password flow the data should be168    collected using form data (instead of JSON) and that it should have the specific169    fields `username` and `password`.170171    All the initialization parameters are extracted from the request.172173    The only difference between `OAuth2PasswordRequestFormStrict` and174    `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the175    client to send the form field `grant_type` with the value `"password"`, which176    is required in the OAuth2 specification (it seems that for no particular reason),177    while for `OAuth2PasswordRequestForm` `grant_type` is optional.178179    Read more about it in the180    [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).181182    ## Example183184    ```python185    from typing import Annotated186187    from fastapi import Depends, FastAPI188    from fastapi.security import OAuth2PasswordRequestForm189190    app = FastAPI()191192193    @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_id201        if form_data.client_secret:202            data["client_secret"] = form_data.client_secret203        return data204    ```205206    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 (`:`) or208    similar, and get the two parts `items` and `read`. Many applications do that to209    group and organize permissions, you could do it as well in your application, just210    know that it is application specific, it's not part of the specification.211212213    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 the215        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_secret222    client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any)223        using HTTP Basic auth, as: client_id:client_secret224    """225226    def __init__(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 string234                "password". This dependency is strict about it. If you want to be235                permissive, use instead the `OAuth2PasswordRequestForm` dependency236                class.237238                Read more about it in the239                [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 name249                `username`.250251                Read more about it in the252                [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 name262                `password`.263264                Read more about it in the265                [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. Each275                scope is also a string.276277                For example, a single string with:278279                ```python280                "items:read items:write users:read profile openid"281                ````282283                would represent the scopes:284285                * `items:read`286                * `items:write`287                * `users:read`288                * `profile`289                * `openid`290291                Read more about it in the292                [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` and303                `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_secret` (and a `client_id`), they can be sent313                as part of the form fields. But the OAuth2 specification recommends314                sending the `client_id` and `client_secret` (if any) using HTTP Basic315                auth.316                """317            ),318        ] = None,319    ):320        super().__init__(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        )328329330class OAuth2(SecurityBase):331    """332    This is the base class for OAuth2 authentication, an instance of it would be used333    as a dependency. All other OAuth2 classes inherit from it and customize it for334    each OAuth2 flow.335336    You normally would not create a new class inheriting from it but use one of the337    existing subclasses, and maybe compose them if you want to support multiple flows.338339    Read more about it in the340    [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/).341    """342343    def __init__(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.359360                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.369370                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 for379                OAuth2 authentication, it will automatically cancel the request and380                send the client an error.381382                If `auto_error` is set to `False`, when the HTTP Authorization header383                is not available, instead of erroring out, the dependency result will384                be `None`.385386                This is useful when you want to have optional authentication.387388                It is also useful when you want to have authentication that can be389                provided in one of multiple optional ways (for example, with OAuth2390                or in a cookie).391                """392            ),393        ] = True,394    ):395        self.model = OAuth2Model(396            flows=cast(OAuthFlowsModel, flows), description=description397        )398        self.scheme_name = scheme_name or self.__class__.__name__399        self.auto_error = auto_error400401    def make_not_authenticated_error(self) -> HTTPException: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.405406        But declaring any other authentication challenge would be application-specific407        as it's not defined in the specification.408409        For practical reasons, this method uses the `Bearer` challenge by default, as410        it's probably the most common one.411412        If you are implementing an OAuth2 authentication scheme other than the provided413        ones in FastAPI (based on bearer tokens), you might want to override this.414415        Ref: https://datatracker.ietf.org/doc/html/rfc6749416        """417        return HTTPException(418            status_code=HTTP_401_UNAUTHORIZED,419            detail="Not authenticated",420            headers={"WWW-Authenticate": "Bearer"},421        )422423    async def __call__(self, request: Request) -> str | None:424        authorization = request.headers.get("Authorization")425        if not authorization:426            if self.auto_error:427                raise self.make_not_authenticated_error()428            else:429                return None430        return authorization431432433class OAuth2PasswordBearer(OAuth2):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.437438    Read more about it in the439    [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/).440    """441442    def __init__(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.450451                Read more about it in the452                [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.461462                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* that471                use this dependency.472473                Read more about it in the474                [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.483484                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 for493                OAuth2 authentication, it will automatically cancel the request and494                send the client an error.495496                If `auto_error` is set to `False`, when the HTTP Authorization header497                is not available, instead of erroring out, the dependency result will498                be `None`.499500                This is useful when you want to have optional authentication.501502                It is also useful when you want to have authentication that can be503                provided in one of multiple optional ways (for example, with OAuth2504                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:518            scopes = {}519        flows = OAuthFlowsModel(520            password=cast(521                Any,522                {523                    "tokenUrl": tokenUrl,524                    "refreshUrl": refreshUrl,525                    "scopes": scopes,526                },527            )528        )529        super().__init__(530            flows=flows,531            scheme_name=scheme_name,532            description=description,533            auto_error=auto_error,534        )535536    async def __call__(self, request: Request) -> str | None:537        authorization = request.headers.get("Authorization")538        scheme, param = get_authorization_scheme_param(authorization)539        if not authorization or scheme.lower() != "bearer":540            if self.auto_error:541                raise self.make_not_authenticated_error()542            else:543                return None544        return param545546547class OAuth2AuthorizationCodeBearer(OAuth2):548    """549    OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code550    flow. An instance of it would be used as a dependency.551    """552553    def __init__(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.577578                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* that587                use this dependency.588                """589            ),590        ] = None,591        description: Annotated[592            str | None,593            Doc(594                """595                Security scheme description.596597                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 for606                OAuth2 authentication, it will automatically cancel the request and607                send the client an error.608609                If `auto_error` is set to `False`, when the HTTP Authorization header610                is not available, instead of erroring out, the dependency result will611                be `None`.612613                This is useful when you want to have optional authentication.614615                It is also useful when you want to have authentication that can be616                provided in one of multiple optional ways (for example, with OAuth2617                or in a cookie).618                """619            ),620        ] = True,621    ):622        if not scopes:623            scopes = {}624        flows = OAuthFlowsModel(625            authorizationCode=cast(626                Any,627                {628                    "authorizationUrl": authorizationUrl,629                    "tokenUrl": tokenUrl,630                    "refreshUrl": refreshUrl,631                    "scopes": scopes,632                },633            )634        )635        super().__init__(636            flows=flows,637            scheme_name=scheme_name,638            description=description,639            auto_error=auto_error,640        )641642    async def __call__(self, request: Request) -> str | None:643        authorization = request.headers.get("Authorization")644        scheme, param = get_authorization_scheme_param(authorization)645        if not authorization or scheme.lower() != "bearer":646            if self.auto_error:647                raise self.make_not_authenticated_error()648            else:649                return None  # pragma: nocover650        return param651652653class SecurityScopes:654    """655    This is a special class that you can define in a parameter in a dependency to656    obtain the OAuth2 scopes required by all the dependencies in the same chain.657658    This way, multiple dependencies can have different scopes, even when used in the659    same *path operation*. And with this, you can access all the scopes required in660    all those dependencies in a single place.661662    Read more about it in the663    [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/).664    """665666    def __init__(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[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[686            str,687            Doc(688                """689                All the scopes required by all the dependencies in a single string690                separated by spaces, as defined in the OAuth2 specification.691                """692            ),693        ] = " ".join(self.scopes)

Code quality findings 2

Ensure functions have docstrings for documentation
missing-docstring
def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
Ensure functions have docstrings for documentation
missing-docstring
def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.