aliev / aioauth

Asynchronous OAuth 2.0 provider for Python 3

Home Page:https://aliev.me/aioauth

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Authorization Code flow with PKCE loses cookies between redirects

mister-px opened this issue · comments

  • aioauth version: ^1.6 (actual on pypi)
  • Python version: 3.9
  • Operating System: linux

Description

I'm building a opinionated all-in-one foundation for web app development, Red Warden.
I'm integrating aioauth in it, and I'm having issues on the AuthCode (PKCE) flow, as it loses the cookies between redirects.

What I Did

I'm using a cookie for session tracking. I assumed the flow involves a redirect if the user is not logged in. So, for example (I'm using Postman web app to execute the flow):

  • GET /oauth2/authorize?...
    the user is not logged in (no session cookie), redirects 302 to /login
  • GET /login
    I enter my credentials and submit
  • POST /login
    credentials are ok, sets a session cookie, redirects to /oauth2/authorize?...
  • GET /oauth2/authorize?...
    the user is logged in, processes correctly the request parameters, proceeds to /oauth2/token
  • POST /oauth2/token
    here the session cookie is lost, so it bounces back to /login

From what i can see the "to_oauth2_request" and "to_fastapi_response" do not care about cookies.
The first thoughts were:

  • subclass the OAuth2Request class and add a "cookies" property (my preferred choice);
  • remove the "logged in" requirement from the POST /token endpoint;

but I'm not sure it's the right way to go.

By the way, this library is awesome! Thank you!

Hi, @mister-px . Thanks for using aioauth!

/token endpoint should not process cookies. This endpoint used for authorization code validation. Basically /oauth2/authorize should produce a redirect to a callback that checks authorization_code with /token endpoint from server side. This endpoint should not be protected by an authorization mechanism.

Here an example with tests:

https://github.com/aliev/aioauth/blob/master/tests/test_flow.py#L181-L187

Curl example:

curl --compressed -v localhost:8080/v1/oauth/tokens \
	-u test_client_1:test_secret \
	-d "grant_type=authorization_code" \
	-d "code=7afb1c55-76e4-4c76-adb7-9d657cb47a27" \
	-d "redirect_uri=https://www.example.com"

P.S. the new version of aioauth with openid support will be released soon (currently it is on master branch). I'm also working now on better fastapi example.

Thank you aliev!

So, this is what I did: i removed the pypi package and installed aioauth from git... a lot of things changed, of course, but i made it work easily as it is well written (kudos again!)

Then, I removed the "logged in" requirement from the my /token endpoint, as you pointed out (I really missed the point in the RFC where it says that it doesn't handle cookies), and I managed to have the correct new tokens in the db after the flow! I'll make more tests tomorrow, but it seems to be working perfectly!

I'm not using FastAPI, as my project is based directly on Starlette. But it's quite similar, i didn't need to modify too much stuff... If I can help with the docs, or the examples, just drop me a line 👍

Just another last-minute doubt: where is it supposed to be stored the logged user info related to the token? I have seen the models, there's nothing related... only in the request, the None check for password grant.

I was thinking about overloading the create_token method in BaseStorage, and encoding + encrypting the info i needed in the access_token and refresh_token fields, like a JWT... or maybe of adding another field in the "tokens" and "authorization codes" tables, that would be good if we need to query all tokens for that particular user.

Do you have a best practice about this?

@mister-px if you want to bind a token to a user, then probably the best option would be to store the user in the Token model itself.

If I can help with the docs, or the examples, just drop me a line

it would be great! @synchronizing was working on documentation in #21 not sure about the status :)

@mister-px not sure, but maybe we should also add the user to Token, AuthorizationCode and maybe even to Client models (Client creator). We also have the user in Request:

user: Optional[Any] = None

@aliev ok, let's recap:

Authorization Code FLOW (with PKCE)

Ok, so the way I understood it - correct me if I'm wrong - is that when you GET the /authorize endpoint the user can be already logged in, or else he'll be redirected to the login page. After a successful login, or if already logged in, the /authorize endpoint will start processing the flow, having the logged in user in the session.
I think it makes sense that both the authorization code and token entities have a NULLABLE field that identifies the user. I don't think it should be added in the client entity, as in my understanding it's just the "vehicle" for the user doing the OAuth2 flow.

I know about the User in the Request, it's passed to the BaseStorage methods so we can value it as a "user_id" (taken from session in the first /authorize, in my case) so that it can be stored along in the auth code and token tables (save_authorization_code, save_token).

I think the "user_id" is enough, with no need for more structure: but this just because I'm used to NOT store the scopes in the token. It's another query to the DB for each API call, but the point is controlling access from the server.

Other flows

I'm still thinking how does this impact on other flows, but having worked a lot in the past with PHPLeague OAuth2 Server I can't see anything critical: in a user-less flow like the "Client Credentials" the NULLABLE user will be empty.

What are your thoughts on this?

is that when you GET the /authorize endpoint the user can be already logged in, or else he'll be redirected to the login page.

authorize endpoint expects authorized user.

After a successful login, or if already logged in, the /authorize endpoint will start processing the flow, having the logged in user in the session.

correct.

Hey @aliev, sorry for the delay. What about the last comment I made? Is that ok for you?

If you agree, I have a branch and a PR ready for this! i'd like to push them, but I haven't the permissions.

@mister-px you can fork this repo and create the PR from your fork.

@aliev, i finished testing the PR. Added code and a couple of new tests. What do you think?

In the end I understood why you said about setting the "user_id" for the "Client" too.

hi @mister-px
I'm not sure that we have to change anything in core of aioauth to support users. I was thinking about adding the user field to the Client, Token and AuthorizationCode models to represent that these models could contain the user object.

I invited you to a private repository with an example of aioauth + FastAPI integration (with users support). You can use it as an example for your project. This project is under development and it will be open as soon as I finish working on it. This will be an illustrative example of using aioauth (with FastAPI).

P.S. aioauth is still under active development, I'm improving it and some APIs may change from release to release

Thank you @aliev, I'll take a look