Refresh session data / identity object

Please, i need help.

When I log in to my application, the user information and permissions are stored in the session’s authentication object.
My User Entity implements the IdentityInterface.

However, if the user itself or an administrator changes the information in the database, this object should be updated in the session.

As a temporary measure, I ran the following command while editing in UserController:

public function edit($id=null) {
...
   $this->refreshSessionData();
...
}

    private function refreshSessionData()
    {
        $user = $this->Users->get($this->Authentication->getIdentity()->get('id'), ['finder' => 'authenticatedUser']);
        $this->Authentication->setIdentity($user);
    }

However, I now had the problem that this only applied to one session, so if I was logged in in another browser with the same user, that data in session was not updated.

Now my approach is to fetch the user data from the database with every request and update the session. I wanted to do this via the AppController::beforeFilter(), but it doesn’t seem to work.

public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);

$user = $this->fetchTable('Users')->get($this->Authentication->getIdentity()->get('id'), ['finder' => 'authenticatedUser']);
$this->Authentication->setIdentity($user);
}

I get the following error message.

Call to undefined method App\Model\Entity\User::can()
in RequestAuthorizationMiddleware…

Okay maybe i found the problem. It wasn’t the RequestAuthorizationMiddleware. The Error occurs in view:

            if ($this->Identity->isLoggedIn() && $this->request->getAttribute('identity')->can('view', new User())) {
                echo $this->Html->link('Benutzer', '/users');
            }

Maybe that’s not best practice, but how can i get Authorization informations in view for navigation links?

It doesn’t seem to be that easy to keep the session data up to date.

The Authentication Component’s setIdentity() method doesn’t seem to do what it’s supposed to do, even though it’s described here:

https://book.cakephp.org/authentication/3/en/migration-from-the-authcomponent.html#checking-identities

In this code snipet from docs you can read the entity from database and set it as identity, but it’s not working…

Here is a working code, although it’s not really nice to do it this way:

public function beforeFilter(EventInterface $event)
{
        parent::beforeFilter($event);

        if ($this->Authentication->getIdentity()) {
            $user = $this->fetchTable('Users')->get($this->Authentication->getIdentity()->get('id'), 
                ['finder' => 'authenticatedUser']);
            $session = $this->request->getSession();
            $session->write('Auth', $user);
        }
}

Any other solution to solve that problem?

If you use the SessionAuthenticator then

$this->Authentication->setIdentity($user);

should keep the session values up2date. You just need to call it whenever the User gets updated.

But if it’s somebody other than the user in question updating the user data (e.g. an admin), this won’t have any effect on the updated user’s session.

Sorry, i did this in AppController’s beforeFilter() method, but that didn’t work, because after that many other components or in view it’s expecting an Authorization/Identity type, but there was a Entity/User type and it couldn’t find the can() method.

Please see my first post.
In UsersController → refreshSessionData() the setIdentity() works well. When i move that to beforeFilter() in AppController it doesn’t work. Maybe because i redirect to another page after set the Identity in UsersController?

If you use the CakeDC/Users plugin, then you can use the AuthLinkHelper that generates an action link only, if the permissions (in the config/permissions.php file) are defined.

<?= $this->AuthLink->link('Users', '/users') ?>

But true, it is a bit complicated to get a good workflow for setting possible actions for a user and then outputting in the view. Because I find, that permissions logic has nothing to do with the view. Instead I implemented some classes (traits, policies, helpers, etc.) where then a controllers beforeRender() method is invoked to get the possible actions for a certain view or entity. This actions are set to the view and then the helper creates me the appropriate action links.

1 Like

However, I now had the problem that this only applied to one session, so if I was logged in in another browser with the same user, that data in session was not updated.

Each browser will have it’s own session record so obviously updating the session info in one browser won’t affect the session in another browser.

To achieve what you want you’ll have to use db based sessions and then search for all records matching the required user id and update them directly in db.

Or else don’t store those details in the session. Perhaps server-side cache with the user ID as the key would be better for this use case, for example.

Thank you for the responses. I found a solution.

I added the identify option to my Authenticator:

            $authenticationService->loadAuthenticator('Authentication.Session', [
                'identify' => true,
                'fields' => [
                    AbstractIdentifier::CREDENTIAL_USERNAME => 'email',
                    AbstractIdentifier::CREDENTIAL_PASSWORD => 'password',
                ],
            ]);

The identify option fetches the current user information from the database and stores it in the request’s identity attribute. It does not refresh the session!

So when the user changes their password in session no. 1, the same user logged out in session no. 2. That’s also exactly what I want.:slightly_smiling_face: