dorinclisu / fastapi-auth0

FastAPI authentication and authorization using auth0.com
MIT License
230 stars 37 forks source link

[Question] Can I combine different auth methods ? #39

Closed accountForIssues closed 10 months ago

accountForIssues commented 10 months ago

Thank you so much for your work so far. It was easy to add Auth0 support to my application.

I have been using it to verify the Bearer token and so far it has worked great. Now, I might have to add cookie support as well.

I was able to adapt your code to add a way to check the Cookie and decode/verify it and return the access_token to the get_user but the FastAPI router dependencies still expect the Bearer token so it fails. Or maybe I went about the wrong way.

Can you let me know if what I want to do is possible ?


# fastapi router

router = APIRouter(
    prefix="/api/user",
    dependencies=[Depends(auth0_object.authcode_scheme)],
    responses=default_responses,
)

# Added to your auth.py

from fastapi.security.api_key import APIKeyCookie

from config import auth0_settings
from utils.auth0_cookie_decryptor import validate_cookie

class AppSessionCookie(APIKeyCookie):
    async def __call__(self, request: Request):
        return await super().__call__(request)

def bearer_or_cookie_check(
    bearer_creds: HTTPAuthorizationCredentials = Security(
        Auth0HTTPBearer(auto_error=False)
    ),
    session_cookie: str = Security(
        AppSessionCookie(name="appSession", auto_error=False)
    ),
):
    if bearer_creds:
        return bearer_creds

    if session_cookie:
        try:
            decrypted_cookie = validate_cookie(
                jwe_secret=auth0_settings.session_secret.get_secret_value(),
                jwe_cookie_string=session_cookie,
            )
            if decrypted_cookie:
                creds = HTTPAuthorizationCredentials(
                    scheme=decrypted_cookie["token_type"],
                    credentials=decrypted_cookie["accessToken"],
                )
                return creds
        except Exception as error:
            logger.warning("Exception while decrypting cookie: %s", str(error.args))
            raise HTTPException(403, detail="Invalid session cookie")

    return None

# and changed the depends get_user

creds: Optional[HTTPAuthorizationCredentials] = Depends(
            bearer_or_cookie_check
        ),  # changed depends

I see that the authcode_scheme is also from the Auth0 class and that is where FastAPI checks and rejects if no bearer is found.

Should I just inherit and override the __call__ like you did for OAuth2ImplicitBearer ? This is only for Swagger so should be fine right ?

The weird thing is I can also see the appSession cookie in Swagger as well. I dunno where it's coming from.

Sorry for the "support" type issue but just wanted to see if I want can be done. Feel free to close it.

EDIT: I realise that the OAuth2AuthorizationCodeBearer is for use to get the access token directly (right ?) so will I lose that if I override the __call__ method ? The cookie will be set with the domain attribute from the frontend so I don't think I care about getting the cookie from Swagger.

I also understand that the actual verification is done in get_user so I can exclude dependencies from the router but I will lose the Swagger docs for auth. I would like to keep both.


UPDATE: Added more info about my goal.

I realised I might not have explained my intentions well.

Currently, I get an access_token from a React SPA and this library validates it.

We have another frontend client using Nextjs. That client doesn't have access to the token, instead it encrypts the access_token and the id_token in a cookie using a shared secret. The Next environment itself uses this cookie for Auth.

Now, this results in client requests being proxied via Next which itself requires duplicate proxy API endpoints in the frontend.

Because, the cookie can be sent across the same domain, we can call the backend and any available cookies can be sent with the request.

What I want to do (or kind of have done) is to read this cookie and decrypt it. Then I extract the access_token and pass that to the get_user function from this library.

The cookie is never modified or sent back to the client. The actual authentication is still done by this library. Any error with cookie validation will result in get_user dependency failure.

accountForIssues commented 10 months ago

EDIT: Disregard this comment. Instead, doing this - https://github.com/dorinclisu/fastapi-auth0/issues/39#issuecomment-1841281241 - works.

~If I add a scheme in Auth0 class such as this:~

        self.custom_scheme = bearer_or_cookie_check

~and then use that as dependency, it seems to work. Can it be improved ?~

accountForIssues commented 10 months ago

Doing this allows for both now.

class Auth0:
    ## code ##
        self.authcode_scheme = OAuth2AuthorizationCodeBearer(
            authorizationUrl=authorization_url,
            tokenUrl=f"https://{domain}/oauth/token",
            scopes=scopes,
            auto_error=False # <-- Added this
        )

Now, if the cookie is available, it gets sent. Header auth also works.

If both are not present, the reverse proxy (Caddy) blocks it.

I'll leave this open for a day or two in case someone else has better ideas and then close it.

dorinclisu commented 10 months ago

Why do you need this? The auth0 sdk for your choice of frontend should already handle the cookies properly, so to me it seems you are trying to do something hacky and possibly introduce vulnerabilities.

accountForIssues commented 10 months ago

It's because a new frontend app/feature is being developed in NextJS and we want to be able to connect directly with our backend rather than via the Next environment. The frontend doesn't have access to an access_token and can only send the secure cookies.

There is this library that is similar to yours but offers the cookie part - https://github.com/TCatshoek/fastapi-nextauth-jwt

Basically, I would like to combine both so that everything keeps working via frontend and/or swagger.

EDIT: Updated my OP with some more info on what my goal is.

accountForIssues commented 10 months ago

We have decided to look for alternative ways and not mess with the auth process. I'm closing this. Feel free to reopen.

The above still does work but might have unknown side effects e.g., the SDK splits the session cookie into multiple cookies if it's larger than 4KB. Supporting that as well is not trivial and there might be more things that come up.

So we decided not to try for this approach.