markitosgv/JWTRefreshTokenBundle

Multiple authentication support

Opened this issue · 1 comments

Fabrn commented

Hello there ! I've been using this bundle since yesterday since it was recommended on the Lexik JWT doc, nethertheless I'm encountering problems with my setup that makes me simply unable to use it for now. I may have understood things wrong and maybe I'm not doing this correctly, you guys should be able to tell me !

For our own purposes, we have two distinct authentications, which are represented by two entites : User and Customer. Of course, there are a custom UserProvider for each of these entity. As I was following the documentation, I added the UserProvider as the configured User Provider as so :

user_provider: security.user.provider.concrete.app_user_provider

Then, I added an EventSubscriber subscribing to the Kernel Response event, in which I was refreshing the token like so :

if (($token = $this->tokenStorage->getToken()) instanceof JWTUserToken)
{
     $user = $token->getUser();

     $refreshTokenObject = $this->refreshTokenManager->getLastFromUsername($user->getEmail());
     $generatedRefreshToken = null;

     if (null !== $refreshTokenObject && $refreshTokenObject->isValid())
     {
        if ($refreshTokenObject->isValid())
        {
            $refreshToken = $refreshTokenObject->getRefreshToken();

            $req = Request::create("/token/refresh", "GET", ["refresh_token" => $refreshToken]);
                    
            /** @var JWTAuthenticationSuccessResponse $response */
            $response = $this->refreshService->refresh($req);

            $newTokens = json_decode($response->getContent(), true);

            $content = json_decode($event->getResponse()->getContent(), true);
                    
            $generatedRefreshToken = $newTokens["refresh_token"];
                    
            $responseContent = [
                "token" => $newTokens["token"],
                "response" => $content
             ];

             $event->setResponse($event->getResponse()->setContent(json_encode($responseContent)));
         }

         $this->refreshTokenManager->delete($refreshTokenObject);
     }
}

Here is the problem : since I was two user providers, if I connect myself on the one for Customer, my Customer instance will be converted into a User instance, which causes crashes as anyone would've expected.

Is there anything I can do about it ?

Thanks for the reply !

#246 added some core framework to help with this, but there's still a long way to go to make things reliable. Even with that PR adding in a "proper" authenticator which can hold different configurations per firewall, there are still some design issues in both this bundle and LexikJWTAuthenticationBundle that make supporting multiple authenticators a bit tricky.

The biggest offender revolves around the lexik_jwt_authentication.handler.authentication_success service and the lexik_jwt_authentication.on_authentication_success event (which this bundle's AttachRefreshTokenOnSuccessListener subscribes to). The success handler calls into an API that only processes a user object and the event it emits only includes that and the data for a JSON response (which has the generated JWT embedded), so there's no reference to the firewall or the security token available (which would be the biggest help in supporting multiple firewalls with different user providers and/or objects). Another potential issue is the fact that the RefreshTokenInterface knows nothing about the associated user object, it's only aware of the user identity (so if you have someone with the same username across multiple firewalls, that might throw a wrench in things).

In a Symfony 5.3 environment, you may be able to work around this by configuring the login and refresh firewalls to avoid the Lexik bundle's service and implement your own custom success handlers (making sure you implement a hybrid of the Lexik bundle's success handler and this bundle's AttachRefreshTokenOnSuccessListener to get the tokens generated properly and attached to the response), and you'd probably need to extend the RefreshTokenInterface to store the FQCN of the associated user object. But, that's just kind of guessing based on what I know of the code in both bundles.