Fastapi: Tutorial on Authorization Code Grant Flow

Created on 25 Jun 2019  路  6Comments  路  Source: tiangolo/fastapi

The example provided in the docs, regarding oauth2, is based on the password flow. However, that flow is only destined to first-party apps, since it requests the user's password and must not be allowed for third-party apps to use (https://oauth.net/2/grant-types/password/). I was planning to build a complete Oauth2 server and expose it publicly, in order to allow any authorized third-party app to act in behalf of a user (if he consents through providing his credentials in my own url and allowing the required scopes). I read the following comment on https://fastapi.tiangolo.com/tutorial/security/oauth2-scopes/ :
_"But if you are building an OAuth2 application that others would connect to (i.e., if you are building an authentication provider equivalent to Facebook, Google, GitHub, etc.) you should use one of the other flows.
The most common is the implicit flow.
The most secure is the code flow, but is more complex to implement as it requires more steps. As it is more complex, many providers end up suggesting the implicit flow."_
Nevertheless, the implicit flow is insecure and not recommended anymore :
_"The implicit grant response type "token") and other response types causing the authorization server to issue access tokens in the authorization response are vulnerable to access token leakage and access token replay as described in Section 4.1, Section 4.2, Section 4.3, and Section 4.6."_
_Source:_ https://tools.ietf.org/html/draft-ietf-oauth-security-topics-12
I suggest a minimal example using the "Authorization Code Grant" flow, since it is more secure and robust.

question

Most helpful comment

I am presently struggling to implement Authorization Code flow. What has me scratching my head is: what magic is taking care of swapping the authorization code for a token?

Using the FastAPI models, there doesn't seem to be a way to specify the redirect URL that the access code is POSTed to after successful authentication.

As it stands, it seems like the Authorization Code flow isn't actually fully implemented in FastAPI - am I missing something?

It is not implemented. I imagine it isn't because it would be a hassle to implement the tests and mocks for it to all work. The documentation for the password flow isn't far off though. Reading the documentation (maybe in dependencies) it states that security can be easily expanded with "depends" and additional libraries added.

Also, OpenAPI specification does not implement the redirect_uri with authorization code flow as it is intended to document the endpoint. https://github.com/OAI/OpenAPI-Specification/issues/1285

I completed an authorization code prototype using authlib for OAuth2 functionality and pyjwt for the token validation. I plan on refactoring the pyjwt component using authlib's native jose library and then doing a write up on it soon.

This is the model for the authorization flow I created (pardon any cruft):

from typing import List, Optional
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.openapi.models import OAuth2 as OAuth2Model, OAuthFlows as OAuthFlowsModel
from fastapi.security.oauth2 import OAuth2
from starlette.requests import Request


class OAuth2AuthorizationCodeBearer(OAuth2):
    def __init__(
        self,
        authorizationUrl: str,
        tokenUrl: str,
        client_id: str = None,
        scheme_name: str = None,
        scopes: dict = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = OAuthFlowsModel(
            authorizationCode={
                "authorizationUrl": authorizationUrl,
                "tokenUrl": tokenUrl,
                "scopes": scopes
            })
        super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)

    async def __call__(self, request: Request) -> Optional[str]:
        authorization: str = request.headers.get("Authorization")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=HTTP_401_UNAUTHORIZED,
                    detail="Unauthorized",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None
        return param

All 6 comments

Agreed. I'd like to see evidence of support for the Authorization Code grant, as well as PKCE, for the many parties interested in building mobile clients

I am presently struggling to implement Authorization Code flow. What has me scratching my head is: what magic is taking care of swapping the authorization code for a token?

Using the FastAPI models, there doesn't seem to be a way to specify the redirect URL that the access code is POSTed to after successful authentication.

As it stands, it seems like the Authorization Code flow isn't actually fully implemented in FastAPI - am I missing something?

I am presently struggling to implement Authorization Code flow. What has me scratching my head is: what magic is taking care of swapping the authorization code for a token?

Using the FastAPI models, there doesn't seem to be a way to specify the redirect URL that the access code is POSTed to after successful authentication.

As it stands, it seems like the Authorization Code flow isn't actually fully implemented in FastAPI - am I missing something?

It is not implemented. I imagine it isn't because it would be a hassle to implement the tests and mocks for it to all work. The documentation for the password flow isn't far off though. Reading the documentation (maybe in dependencies) it states that security can be easily expanded with "depends" and additional libraries added.

Also, OpenAPI specification does not implement the redirect_uri with authorization code flow as it is intended to document the endpoint. https://github.com/OAI/OpenAPI-Specification/issues/1285

I completed an authorization code prototype using authlib for OAuth2 functionality and pyjwt for the token validation. I plan on refactoring the pyjwt component using authlib's native jose library and then doing a write up on it soon.

This is the model for the authorization flow I created (pardon any cruft):

from typing import List, Optional
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.openapi.models import OAuth2 as OAuth2Model, OAuthFlows as OAuthFlowsModel
from fastapi.security.oauth2 import OAuth2
from starlette.requests import Request


class OAuth2AuthorizationCodeBearer(OAuth2):
    def __init__(
        self,
        authorizationUrl: str,
        tokenUrl: str,
        client_id: str = None,
        scheme_name: str = None,
        scopes: dict = None,
        auto_error: bool = True,
    ):
        if not scopes:
            scopes = {}
        flows = OAuthFlowsModel(
            authorizationCode={
                "authorizationUrl": authorizationUrl,
                "tokenUrl": tokenUrl,
                "scopes": scopes
            })
        super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)

    async def __call__(self, request: Request) -> Optional[str]:
        authorization: str = request.headers.get("Authorization")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=HTTP_401_UNAUTHORIZED,
                    detail="Unauthorized",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None
        return param

(Disclaimer: I've been scratching my head trying to figure out how SwaggerUI fits in the OAuth puzzle for a few days now and am only just starting to understand OAuth, so I could be wrong on most of this)

One thing that should be mentioned when that section of the tutorial is written is that the way OpenAPI understands the OAuth flows means that the server whose API is being described is always considered (in OAuth parlance) as the "Resource Server", not the "Client".

This means that a FastAPI application written to connect to some Google service to, say, perform image processing on the user's Google Photo collection shouldn't be using OAuth2AuthorizationCodeBearer to communicate what scopes it needs to obtain from Google and let users login to Google from the API documentation page because the FastAPI application does not actually own any of the resources locked behind that authentication requirement; Google does. This isn't an issue with OAuth2PasswordBearer because the "Ressource Owner Password" flow is supposed to essentially only be used by applications that the user fully trusts with the credentials to the service being given, which in that case happen to be the same FastAPI application, so there's no difference between the "Resource Server" and the "Authorization Server" that the application developer needs to understand.

Before moving on to other authentication flows, the tutorial should then make it clear that OAuth2AuthorizationCodeBearer isn't intended for applications that need to use OAuth to act as "user agents" for another service (that would be an OAuth Client) or to defer all authentication to a separate service such that the only way you can login is via Facebook/Twitter/Google (that would be an OpenID Connect relaying party), and is instead meant to enable other clients to access resources controlled by the FastAPI application.

@sm-Fifteen There are two separate components in this context: a client and resource server. (SwaggerUI in your browser is a client)

This OAuth2AuthorizationCodeBearer purpose is to protect contents of a resource server. A client's purpose is to connect to a resource server. A resource server can optionally have a client to connect to other resource servers.

The reason I explain the above is that you are potentially conflating the two in your example and it will help you clarify your request - which isn't wrong.

Your example of a FastAPI connecting to a Google service protected with OAuth would require a client. This client would require scopes to access that service. This is technically outside the feature set of what FastAPI provides.

Now, if you were using Google to protect your service built using FastAPI this resource protector would be useful.

Hope this helps.

Was this page helpful?
0 / 5 - 0 ratings