Conflict between impersonation and CookieAuthenticator using Authentication plugin

2023-02-12 04:27:54 error: [InvalidArgumentException] The Authentication\Authenticator\CookieAuthenticator Provider must implement ImpersonationInterface in order to use impersonation. in …/vendor/cakephp/authentication/src/AuthenticationService.php on line 496

I’m having trouble to impersonate users when I change my Session Authenticator configuration. If I keep ‘identify’ as false, the error goes away. Is this functionality intended?
Thanks in advance.

$usuario = $this->Usuarios->get($id);
$this->Authentication->impersonate($usuario);
    public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $fields = [
            IdentifierInterface::CREDENTIAL_USERNAME => 'email',
            IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        $login = Router::url([
            'prefix' => false,
            'controller' => 'Users',
            'action' => 'login'
        ]);

        $service = new AuthenticationService(['unauthenticatedRedirect' => $login]);

        $service->loadAuthenticator('Authentication.Session', ['identify' => true]);

        $service->loadAuthenticator('Authentication.Form', [
            'fields' => $fields,
            'loginUrl' => $login
        ]);

        $service->loadAuthenticator('Authentication.Cookie', [
            'fields' => $fields,
            'cookie' => ['name' => 'my_cookie_auth']
        ]);

        $service->loadIdentifier('Authentication.Password', [
            'resolver' => [
                'className' => 'Authentication.Orm',
                'userModel' => UsersTable::class,
                'finder' => 'active',
            ],
            'fields' => $fields,
        ]);

        return $service;
    }

PD: I’m aware that I can implement the ImpersonationInterface but maybe that’s not necessary or I’m missing something in my getAuthenticationService()

The cookie authenticator intentionally doesn’t support impersonation, as this is supposed to be a session bound feature, meaning once the session expires, the impersonation should stop. The cookie authenticator does “extend” the session so to speak, which would extend the impersonation accordingly.

I haven’t used that feature yet, so I’m not sure whether what you’re seeing is expected. You may want to elaborate on the exact circumstances in which the error is happening, ie explain how one can reliably reproduce the problem, for example where exactly you do trigger impersonation, whether the error happens only when you log in, does it always happen on every request, does it happen only on requests where the session expires and the cookie authenticator logs you in again, etc…

Technically there should be no difference for the session authenticator between identify being true or false, except when something has changed in the data storage so that the identification fails, which is usually a rather rare circumstance.

I created a new Bitbucket project with the bare minimum to reproduce the problem.

Keeping the identify option to true will throw “The Authentication\Authenticator\CookieAuthenticator Provider must implement ImpersonationInterface in order to use impersonation”.

        $service->loadAuthenticator('Authentication.Session', [
            'identify' => true
        ]);

Steps to reproduce the problem:

  1. bin/cake migrations migrate
  2. bin/cake migrations seed
  3. bin/cake server -p 8765
  4. Visit http://localhost:8765
  5. Log in. Username: test@test.com. Password: test
  6. Click the impersonate button

I can’t test it right now, but I’ve just noticed that you don’t map the username field for the session authenticator, which will likely cause it to always fail to identify the identity, as you’re using a non-default username column.

1 Like

Thanks, that’s pretty helpful. Also it is worth to note that the SessionAuthenticator ‘fields’ array slightly differ from the other authenticators.

This is the working code

 public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $service = new AuthenticationService();

        // Define where users should be redirected to when they are not authenticated
        $service->setConfig([
            'unauthenticatedRedirect' => Router::url([
                'prefix' => false,
                'plugin' => null,
                'controller' => 'Users',
                'action' => 'login',
            ]),
            'queryParam' => 'redirect',
        ]);

        // Load the authenticators. Session should be first.
        $service->loadAuthenticator('Authentication.Session', [
            'fields' => [
                IdentifierInterface::CREDENTIAL_USERNAME => 'email'
            ],
            'identify' => true
        ]);

        $service->loadAuthenticator('Authentication.Cookie', [
            'fields' => [
                IdentifierInterface::CREDENTIAL_USERNAME => 'email',
                IdentifierInterface::CREDENTIAL_PASSWORD => 'password',
            ],
        ]);

        $service->loadAuthenticator('Authentication.Form', [
            'fields' => [
                IdentifierInterface::CREDENTIAL_USERNAME => 'email',
                IdentifierInterface::CREDENTIAL_PASSWORD => 'password',
            ],
        ]);

        // Load identifiers
        $service->loadIdentifier('Authentication.Password', [
            'fields' => [
                IdentifierInterface::CREDENTIAL_USERNAME => 'email',
                IdentifierInterface::CREDENTIAL_PASSWORD => 'password',
            ],
        ]);

        return $service;
    }