fastapi
fastapi copied to clipboard
Tutorial on Authorization Code Grant Flow
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.
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
https://github.com/tiangolo/fastapi/pull/797
(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.
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.
@mr-bjerre OIDC does that specifically. This implementation is more for instances that would sit behind an API gateway or authentication proxy.
@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.
@mr-bjerre FastAPI uses the OpenAPI spec which is also used for SSO capabilities.
https://swagger.io/docs/specification/authentication/openid-connect-discovery/
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!
@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).
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.
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()
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.
Everywhere you stated
resource server
should sayauthorization 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.
@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!
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"
Hi, Any news concerning code flow ?
Thanks