Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tutorial on Authorization Code Grant Flow #335

Closed
jonDel opened this issue Jun 25, 2019 · 19 comments
Closed

Tutorial on Authorization Code Grant Flow #335

jonDel opened this issue Jun 25, 2019 · 19 comments

Comments

@jonDel
Copy link

jonDel commented Jun 25, 2019

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.

@jonDel jonDel added the question Question or problem label Jun 25, 2019
@jbuusao
Copy link

jbuusao commented Jul 30, 2019

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

@jonathanunderwood
Copy link
Contributor

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?

@kuwv
Copy link
Contributor

kuwv commented Oct 24, 2019

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. OAI/OpenAPI-Specification#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

@kuwv
Copy link
Contributor

kuwv commented Dec 18, 2019

#797

@sm-Fifteen
Copy link
Contributor

sm-Fifteen commented Sep 21, 2020

(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.

@kuwv
Copy link
Contributor

kuwv commented Sep 25, 2020

@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.

@NixBiks
Copy link

NixBiks commented Jan 7, 2021

Is it possible to have a custom callback/redirect_url using OAuth2AuthorizationCodeBearer?

My use case: I have a logged in user already. Now this user needs to authenticate a thirdparty service using OAuth2 PKCE (Code flow). The server needs to combine the logged in user with the thirdparty credentials. I've implemented this by creating my own /login and /redirect endpoints. In the login I save user information in request.session using SessionMiddleware. In /redirect I can fetch the corresponding data in request.session by using the state parameter.

This solution works but I'd prefer using OAuth2AuthorizationCodeBearer though and be able to user the swagger ui. The issue is that state and code is sent to /docs/redirect-url which is out of my hands.

@kuwv
Copy link
Contributor

kuwv commented Jan 7, 2021

@mr-bjerre OIDC does that specifically. This implementation is more for instances that would sit behind an API gateway or authentication proxy.

@NixBiks
Copy link

NixBiks commented Jan 7, 2021

@kuwv I see.

I'm a rookie in these flows unfortunately. Does there exist any examples on OpenID Connect for FastAPI? I can't seem to find the sufficient guidelines for myself. Looking at OpenIdConnect I can see that I can only provide a single url. My best guess (and only) is to set that to my /login endpoint but it doesn't seem to work.

If I make the following scheme

openid_connect_scheme = OpenIdConnect(openIdConnectUrl="login")

and add openid_connect_scheme as a dependency in /ping for instance. Then no OpenID Connect authentication appears in swagger ui.

@kuwv
Copy link
Contributor

kuwv commented Jan 11, 2021

@mr-bjerre FastAPI uses the OpenAPI spec which is also used for SSO capabilities.

https://swagger.io/docs/specification/authentication/openid-connect-discovery/

@wotori
Copy link

wotori commented Nov 9, 2021

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. OAI/OpenAPI-Specification#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

what kind of response should authorizationUrl rout return to made this work?
Can you share brief test case?
Thanks!

@kuwv
Copy link
Contributor

kuwv commented Nov 10, 2021

@wotori There are authorization tests written. You could do a quick search for OAuth2AuthorizationCodeBearer for additional details.

The get_authorization_scheme_param just takes the bearer: <token> and returns the token (param).

@GenerallyClueless
Copy link

I am also curious to learn how to use authorization code flow with FastAPI. I have only started testing this today, however I have noticed all of the examples are based upon credentials flow.

I am building a web app using FastAPI to handle requests from multiple users. The web app needs to authenticate with a third-party API. I realize that I do not necessarily need FastAPI since this is more of a "client" application. However, I was interested in using FastAPI & uvicorn so I could take advantage of using concurrent calls.

I hope that in the future, there will be more support for authenticating with Authorization Code flow.

@ithilelda
Copy link

ithilelda commented Aug 3, 2022

In the password flow, the resource (the user's info, like avatar, name etc.) lives on the same server you send your password to, so the server could get you your info right away.
In the code flow, your resource lives on the resource server (google, facebook, twitter etc.), there is usually another authorization server that authorizes you to access the resource, but they usually are provided by the same domain(so your profile is on google, and google is also responsible for authorizing you to access that profile). The app that you want to use the avatar and other info in is actually called a client. the owner of the info is called the user. so when you the user click that login with google icon, you go to the authorization server's (google) endpoint called authorizationUrl. Your login credential is submitted there and google gives back a code to the user. Then the code is used to visit the authorization server's (google) tokenUrl to get the token, and that token is finally used by the client to get the user's resource on the resource server(still on google).
with those terms cleared up, you can certainly see how you would be able to use the OAuth2AuthorizationCodeBearer class. just set the authorizationUrl and the tokenUrl to an auth server (like google), and you can then depend on it in your route where you need to access the user's resource.
for example:

from fastapi.security import OAuth2AuthorizationCodeBearer
import requests

oauth2_google = OAuth2AuthorizationCodeBearer(
    authorizationUrl="https://accounts.google.com/o/oauth2/v2/auth",
    tokenUrl="https://accounts.google.com/o/oauth2/v2/token"
)

@app.get("/users/my_profile")
def get_my_google_profile(token: str = Depends(oauth2_google)):
    # I have no idea where to get user profile on google or what the parameters are. It is vendor specific.
    # Check their doc.
    with requests.get("https://google's profile getting api", params={"access_token" : token}) as response:
        return response.json()

@JarroVGIT
Copy link
Contributor

Everywhere you stated resource server should say authorization server, I think. The Google API you are going to query with the Google access_token is the resource server.

@ithilelda
Copy link

Everywhere you stated resource server should say authorization server, I think. The Google API you are going to query with the Google access_token is the resource server.

yeah, these two just usually live together and I got them confused. I'll edit the post for accuracy.

@MasterGroosha
Copy link

@ithilelda Thank you for the example!
Could you please ELI5 one more thing: how should I implement a /login path flow?

So if the user has the required header/cookie, they're simply redirected to some other resource, like /dashboard.
And if the used does not have the header/cookie, they should be redirected to third-party provider autorize page like https://accounts.google.com/o/oauth2/v2/auth ?

Thanks in advance!

@Nilster
Copy link

Nilster commented Oct 6, 2022

I had similar issue when oauth2 redirection was failing because of the default redirect url is set to /oauth2-redirect.html. I found a way around by adding redirect_uri in the authorization url
authorizationUrl="https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=http://localhost:8082/auth"

@michael-sicpa
Copy link

Hi,
Any news concerning code flow ?

Thanks

Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #9141 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests