[CakePHP 4.x] Impersonate Login as different user

Hello everyone,

I built a classic application where users log in to enter some profile data and upload or edit pictures. The login follows the convention presented in the CMS tutorial which boils down to as little as a single line in the login action: $this->Authentication->getResult();
Obviously the getResult() function detects POST data and performs the user authentication based on username and password provided within. That’s very convenient for such a common case, but

Now I want administrators to be able to edit user profile data and user-attached images. Of course I could build separate templates and actions that are exclusive to administrators and take an user id as a parameter, but it would be so much more elegant to simply enable admins to impersonate a user. That way, the very same views and actions that are already built for the user would allows administrators to edit user data, just as if they were the users themselves.

But how would I do that with the current Authentication plugin?

Bonus question:
The documentation refers to the Authentication plugin, sometimes Authentication middleware, as well as the Authentication component. There are several way to load authentication software into the application:

  • in src/Application.php via $this->addPlugin('Authentication')
  • in src/Application.php via $middlewareQueue->add(new AuthenticationMiddleware($this))
  • in src/Controller/AppController.php via $this->loadComponent('Authentication.Authentication')

I assume at least two of these three plugins/middlewares/components are actually the same, but I’m not certain. What do these have in common? Are they distinct parts? Are they interchangable?

Thank you in advance,
cheers.

1 Like

I can’t help you with the masquerade functionality because as far as I know this isn’t “prepared” in the base authentication plugin. You would have to get a better understanding of how the authentication (and also the authorization) plugins work so you can maybe create such a masquerade functionality yourself.

But I can tell you the difference between the addPlugin(), Middleware and Component confusion.

First of all the addPlugin function is needed so that cakephp and therefore your app knows it should load the authentication module. Without that any hooks, commands or other functionality defined in the src/Plugin.php won’t load (but generic PHP classes which can be autoloaded work without that)

The Middleware is more of a general software design tool to basically transform the data coming into (and sometimes out) of cakephp (and therefore your app) into something more usefull. This happens BEFORE it even gets to your main code parts like in the controllers. see Middleware - 4.x about how middlewares generally work.

In this case the AuthenticationMiddleware checks if your current request is authenticated and if so loads your “identity” so it can then be used in the application.

And the last one is the AuthenticationComponent.
In Cakephp a component is used to gather functionality that is/can be used in multiple controllers. So basically if you load the AuthenticationComponent in your AppController you have access to some authentication functions inside your controller to e.g. skip the authentication for specific functions or get the current logged in user etc.

1 Like

In Cake 3.x I used $this->Auth->setUser() but now I had to port it to 4.x
I wrote a simple Authenticator based on SessionAuthenticator.

<?php
// src/Authenticator/SessionImpersonateAuthenticator.php
declare(strict_types=1);

namespace App\Authenticator;

use ArrayAccess;
use ArrayObject;
use Authentication\Authenticator\AbstractAuthenticator;
use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Psr\Http\Message\ServerRequestInterface;

class SessionImpersonateAuthenticator extends AbstractAuthenticator
{
    /**
     * Default config for this object.
     * - `sessionKey` Session key.
     *
     * @var array
     */
    protected $_defaultConfig = [
        'sessionKey' => 'impersonating',
    ];

    /**
     * @inheritDoc
     */
    public function authenticate(ServerRequestInterface $request): ResultInterface
    {
        $sessionKey = $this->getConfig('sessionKey');
        /** @var \Cake\Http\Session $session */
        $session = $request->getAttribute('session');
        $user = $session->read($sessionKey);

        if (!$user) {
            return new Result(null, ResultInterface::FAILURE_IDENTITY_NOT_FOUND);
        }

        if (!($user instanceof ArrayAccess)) {
            $user = new ArrayObject($user);
        }

        return new Result($user, ResultInterface::SUCCESS);
    }
}

To use it I made two actions in my UserController

public function impersonate($id) {
    // you should handle Authorization here
    // or anyone could impersonate anyone!
    $user = $this->Users->get($id);
    $this->request->getSession()->write('impersonating', $user);
    return $this->redirect('/');
}

public function stopImpersonate($id) {
    $this->request->getSession()->delete('impersonating');
    return $this->redirect('/');
}

To use the Authenticator, in your Application simply load before SessionAuthenticator

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();
    $service->loadIdentifier('Authentication.Password', [
        // ...
    ]);
    $service->loadAuthenticator(Authenticator\SessionImpersonateAuthenticator::class);
    $service->loadAuthenticator('Authentication.Session');
    $service->loadAuthenticator('Authentication.Form', [
        // ...
    ]);

    return $service;
}

If you use stateless authentication, you’ll have to modify the authenticator

2 Likes

Thank you! This is exactly what I was looking for, and your code looks very sleek and cleanly integrates into CakePHP’s convention. I hope you’ll either make an official plugin out of it, or CakePHP’s developers take note and integrate your addition right into their authenticator.

As far as I can see the Authenticator @raul338 created does allow anyone to impersonate as anyone.

I understand that its just a simple proof of concept how its working but for a real implementation/plugin there should be some sort of authorization happening that e.g. only superadmins can impersonate or only users with a specific role

Thanks!

Sorry, It was my IDE that put the use at top and didn’t paste it

Yes but that is why AuthComponent split into 2 plugins. I handle Authorization via a wrapper middleware I wrote of the ACL plugin so that authorization is handle in other code.

I didn’t try the new Authorization plugin, but yes, you should handle your auth code whether be in the controller, middleware or anywhere else.

EDIT: I edited the code to clarify about authorization!

1 Like

I added an issue on the cakephp/authentication github repo to see what the plugin developers think about it.