1

I'm working on a PHP application where there are two levels of authorizations.

On the first and classic level : the user logs in the application, the authentication system sets his roles and registers them in the auth token all right. Let's say out user has ROLE_USER and ROLE_ADMIN.

Now, for some reason I've been asked to dynamically add another role after authentication...

After the official documentation : https://symfony.com/doc/5.4/security/user_providers.html#creating-a-custom-user-provider I tried to achieve this with a custom user provider :

security.yaml :

providers:
    users_in_memory: { memory: null }
    app_user_provider:
      id: App\Security\UserProvider

The UserProvider class has its refreshUser() method called with every request :

class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    //...
    public function refreshUser(UserInterface $user): UserInterface|User
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException('nope');
        }

        $refreshedUser = $this->userRepository->find($user->getId());
        if (!$refreshedUser) {
            throw new UserNotFoundException('nope nope');
        }

        $refreshedUser->addRole('ROLE_BONUS'); // new role added !
        return $refreshedUser;
    }
}

Of course, the User class implements the EquatableInterface so the user is not de-authenticated and logged out by Symfony after the refresh.

Unfortunately, even if the auth token has refreshed its user as expected, it has not refreshed the list of roles given by AbstractToken::getRoleNames(). And when I check the granted new role in a controller, the check fails :

class DashboardInfosController extends AbstractController
{
    public function renderPageWithBonusRole(): Response
    {
        $user = $this->getUser(); // this is the refreshed user
        $roles = $user->getRoles(); // gives the correct roles, including ROLE_BONUS
        $test = $this->isGranted('ROLE_BONUS'); // fails : only ROLE_USER & ROLE_ADMIN are checked here
       
        return $this->render('site/_mypartial.html.twig');
    }
}

After the code I see in the AbstractToken class, rolenames are only set in the constructor and never updated after this point. I've been struggling with this for a day and more, now, and still stuck... So, how can I tell the Security layer of Symfony 5.4 that it has to check the new roles list for my user ?

ChristopheBrun
  • 1,237
  • 7
  • 22
  • What is the use case for this "dynamic role" ? Looks like you are using the wrong tool here. – AymDev Feb 14 '23 at 16:19
  • I only give here a very simplified example. In the real app, the user has to connect to parts of the main application and his roles will change depending on what part he is working on. – ChristopheBrun Feb 14 '23 at 16:24
  • Roles are meant to be tied to the users in the database. Maybe using the session to store the "dynamic roles" and check access rights using voters would be a better idea ? – AymDev Feb 14 '23 at 16:40
  • In the real app, the 'bonus roles' will be loaded from the database indeed. I have to use a Sonata Admin backend and to set the user's roles dynamically for it. No idea how to set voters for this but I'm not very familiar with Symfony 5 and even less with Sonata Admin... – ChristopheBrun Feb 14 '23 at 16:46
  • 2
    I'm not familiar with Sonata either. But when you need to check access rights dynamically, you should go with [voters](https://symfony.com/doc/current/security/voters.html), they are easy to use. A huge downside with your current solution is that if any part of the app calls `EntityManagerInterface::flush()`, your user will be updated with the `ROLE_BONUS` in the database and he will always have it. I don't think you'd want that. – AymDev Feb 14 '23 at 16:52
  • In this case roles are not stored in an attribute tagged as ORM column so flush() will not break anything. Anyway, messing with entities in session is definitely a risky move, I'll keep your warning in mind as a general rule. As for the voters, I'll give a try :p – ChristopheBrun Feb 15 '23 at 08:01
  • Oh right, I wrongly assumed the ORM was involved. I agree that it would be risky to put entities in session but you would only need to store the dynamic roles, nothing about the entity. Have fun :) – AymDev Feb 15 '23 at 08:36

0 Answers0