Rodgard
Rodgard

Reputation: 119

Reload user role after change without re-logging

How to refresh logged in user role e.g. when it has been changed by admin user? I've found the always_authenticate_before_granting security option (it's not included in Symfony 4 documentation) and set it to true.

security.yaml:

security:
    always_authenticate_before_granting: true
    encoders:
        App\Entity\Main\User:
            algorithm: bcrypt

    providers:
        app_user_provider:
            entity:
                class: App\Entity\Main\User
                property: email
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            form_login:
                login_path: login
                check_path: login
            logout:
                path: logout
                target: homepage
            remember_me:
                secret:   '%kernel.secret%'
                path:     /
    access_control:
        - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/.*, roles: ROLE_USER }

but it doesn't take effect.

UPDATE

I've created onRequest subscriber:

class RequestSubscriber implements EventSubscriberInterface
{
    private $tokenStorage;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::REQUEST => 'onRequest'
        ];
    }

    public function onRequest(GetResponseEvent $event): void
    {
        if (!$event->isMasterRequest()) {
            return;
        }

        if(!$token = $this->tokenStorage->getToken()) return;

        $sessionUser = $token->getUser();

        if ($sessionUser instanceof User) {
            $this->tokenStorage->setToken(new PostAuthenticationGuardToken($sessionUser, 'main', $sessionUser->getRoles()));
        }
    }
}

and now I can refresh the updated roles on every request, but comparing sessionUser to databaseUser is pointless, because the sessionUser always contains newly updated roles, though in Symfony Profiler > Security Token are listed the old ones (in case when I don't set the new token, of course).

Upvotes: 3

Views: 3558

Answers (1)

Mike Doe
Mike Doe

Reputation: 17566

Tl;dr I'm afraid you will have to introduce a mechanism of your own in order to make this work.

The session token is stored inside the user's session. This will have quite an impact on your application's performance, because each time a call to the database will be required in order to check if the role had changed.

So you will need a request listener which will compare database role with current user role, and if it is not same, replace the token in the session, this time with new role list, eg. (pseudo code):

sessionUser = tokenStorage.token.user
databaseUser = userRepository.find(sessionUser.id)

if sessionUser.roles !== databaseUser.roles
    tokenStorage.setToken(new PostAuthenticationGuardToken(…))

or use a cache as a flag carrier to notify the user about the change. This method is going to be much quicker for sure.

sessionUser = tokenStorage.token.user

// of course the flag has to be set when the user's role gets changed
cacheToken = 'users.role_reload.' . sessionUser.id

if cache.has(cacheToken)
    databaseUser = userRepository.find(sessionUser.id)
    tokenStorage.setToken(new PostAuthenticationGuardToken(…))
    cache.remove(cacheToken)

Either way the user has to ask the application has there been role change, on each request.

Upvotes: 1

Related Questions