Symfony: How do I refresh the authenticated user from the database?
Asked Answered
P

5

16

Say for example I grant a new role to the currently authenticated user in a controller, like so:

$em = $this->getDoctrine()->getManager();
$loggedInUser = $this->get('security.context')->getToken()->getUser();
$loggedInUser->addRole('ROLE_XYZ');

$em->persist($loggedInUser);
$em->flush();

On the next page load, when I grab the authenticated user again:

$loggedInUser = $this->get('security.context')->getToken()->getUser();

They are not granted the role. I am guessing this is because the user is stored in the session and needs to be refreshed.

How do I do this?

I am using FOSUserBundle if that makes a difference.

EDIT: This question was originally asked in the context of Symfony version 2.3 but there are answers for more recent versions below as well.

Precipitant answered 31/10, 2013 at 18:14 Comment(5)
Is the database updated?Calvin
Yes the database is updated. So if I log out and in again then the new role is reflected.Precipitant
I was checking for the new role in a twig template using if is_granted('ROLE_XYZ'). I changed this to if app.user.hasRole('ROLE_XYZ') and now it is working. Strange...Precipitant
@Precipitant - that's because hasRole checks for the exact roles. It ignores the role hierarchy.. Eg: is ROLE_SUPER_ADMIN has ROLE_USER, ROLE_ADMIN.. If you check hasRole('ROLE_ADMIN')--- false, hasGranted('ROLE_ADMIN')--- trueEcology
Thanks Alex, however, the call to is_granted('ROLE_XYZ') does return TRUE as soon as I have logged out and back in again.Precipitant
M
23

Try this:

$em = $this->getDoctrine()->getManager();
$loggedInUser = $this->get('security.context')->getToken()->getUser();
$loggedInUser->addRole('ROLE_XYZ');

$em->persist($loggedInUser);
$em->flush();

$token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken(
  $loggedInUser,
  null,
  'main',
  $loggedInUser->getRoles()
);

$this->container->get('security.context')->setToken($token);
Marek answered 31/10, 2013 at 22:19 Comment(2)
Thanks! As you've shown, the trick was to replace the security token with a new one with the new roles.Precipitant
I can confirm that this solution still works on 2.6.3Equestrienne
C
15

There's no need for the token reset in the previous answer. Just, in your security config file (security.yml, etc...), add this:

security:
    always_authenticate_before_granting: true
Cornela answered 28/2, 2015 at 12:47 Comment(2)
If this is a point relating to another answer I think it would be better suited to being a comment on that answer, rather than an answer in it's own rightPasticcio
Apparently you need some reputation to do that. Smart moves all arround.Cornela
S
14

While an answer is accepted, Symfony actually has a native way to refresh the User object. Credit goes out to Joeri Timmermans for this article.

Steps for refreshing the User object:

  1. Make your User entity implement the interface

Symfony\Component\Security\Core\User\EquatableInterface

  1. Implement the abstract function isEqualTo:

public function isEqualTo(UserInterface $user)
{
    if ($user instanceof User) {
        // Check that the roles are the same, in any order
        $isEqual = count($this->getRoles()) == count($user->getRoles());
        if ($isEqual) {
            foreach($this->getRoles() as $role) {
                $isEqual = $isEqual && in_array($role, $user->getRoles());
            }
        }
        return $isEqual;
    }

    return false;
}

The code above refreshes the User object if any new roles are added. The same principle also holds true for other fields you compare.

Stowell answered 23/8, 2015 at 21:46 Comment(3)
Thanks for this!, until now I didn't understand EquatableInterface!Elwina
Just a FYI: If you implement EquatableInterface, you may also want to add the other comparisons that Symfony would normally make (until you implemented EquatableInterface). If your User also implements AdvancedUserInterface, then you may want to add comparisons for isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, and isEnabled. Check out github.com/symfony/security/blob/v3.1.2/Core/Authentication/…Lemmons
FYI: the AdvancedUserInterface is deprecated now in favor of a UserCheckerRess
S
4
$user = $this->getUser();
$userManager = $this->get('fos_user.user_manager');
$user->addRole('ROLE_TEACHER');
$userManager->updateUser($user);
$newtoken = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user,null,'main', $user->getRoles());
$token = $this->get('security.token_storage')->setToken($newtoken);
Seleucid answered 16/10, 2017 at 18:13 Comment(1)
security.context didn't work in Symfony 4 for me, but security.token_storage did the thing. Thanks!Equate
S
3

In Symfony 4

public function somename(ObjectManager $om, TokenStorageInterface $ts)
    {
        $user = $this->getUser();
        if ($user) {
            $user->setRoles(['ROLE_VIP']); //change/update role
            // persist if need
            $om->flush();
            $ts->setToken(
                new PostAuthenticationGuardToken($user, 'main', $user->getRoles())
            );
            //...
        } else {
            //...
        }
    }
Shoulders answered 27/8, 2019 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.