.. index::
single: Security; Impersonating User
How to Impersonate a User
=========================
Sometimes, it's useful to be able to switch from one user to another without
having to log out and log in again (for instance when you are debugging something
a user sees that you can't reproduce).
.. caution::
User impersonation is not compatible with some authentication mechanisms
(e.g. ``REMOTE_USER``) where the authentication information is expected to be
sent on each request.
Impersonating the user can be done by activating the ``switch_user`` firewall
listener:
.. configuration-block::
.. code-block:: yaml
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
switch_user: true
.. code-block:: xml
.. code-block:: php
// config/packages/security.php
$container->loadFromExtension('security', [
// ...
'firewalls' => [
'main'=> [
// ...
'switch_user' => true,
],
],
]);
To switch to another user, add a query string with the ``_switch_user``
parameter and the username (or whatever field our user provider uses to load users)
as the value to the current URL:
.. code-block:: text
http://example.com/somewhere?_switch_user=thomas
To switch back to the original user, use the special ``_exit`` username:
.. code-block:: text
http://example.com/somewhere?_switch_user=_exit
This feature is only available to users with a special role called ``ROLE_ALLOWED_TO_SWITCH``.
Using :ref:`role_hierarchy ` is a great way to give this
role to the users that need it.
Knowing When Impersonation Is Active
------------------------------------
You can use the special attribute ``IS_IMPERSONATOR`` to check if the
impersonation is active in this session. Use this special role, for
instance, to show a link to exit impersonation in a template:
.. code-block:: html+twig
{% if is_granted('IS_IMPERSONATOR') %}
Exit impersonation
{% endif %}
.. versionadded:: 5.1
The ``IS_IMPERSONATOR`` was introduced in Symfony 5.1. Use
``ROLE_PREVIOUS_ADMIN`` prior to Symfony 5.1.
Finding the Original User
-------------------------
In some cases, you may need to get the object that represents the impersonator
user rather than the impersonated user. When a user is impersonated the token
stored in the token storage will be a ``SwitchUserToken`` instance. Use the
following snippet to obtain the original token which gives you access to
the impersonator user::
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Security;
// ...
class SomeService
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function someMethod()
{
// ...
$token = $this->security->getToken();
if ($token instanceof SwitchUserToken) {
$impersonatorUser = $token->getOriginalToken()->getUser();
}
// ...
}
}
Controlling the Query Parameter
-------------------------------
This feature needs to be available only to a restricted group of users.
By default, access is restricted to users having the ``ROLE_ALLOWED_TO_SWITCH``
role. The name of this role can be modified via the ``role`` setting. You can
also adjust the query parameter name via the ``parameter`` setting:
.. configuration-block::
.. code-block:: yaml
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
.. code-block:: xml
.. code-block:: php
// config/packages/security.php
$container->loadFromExtension('security', [
// ...
'firewalls' => [
'main'=> [
// ...
'switch_user' => [
'role' => 'ROLE_ADMIN',
'parameter' => '_want_to_be_this_user',
],
],
],
]);
Limiting User Switching
-----------------------
If you need more control over user switching, you can use a security voter. First,
configure ``switch_user`` to check for some new, custom attribute. This can be
anything, but *cannot* start with ``ROLE_`` (to enforce that only your voter will
be called):
.. configuration-block::
.. code-block:: yaml
# config/packages/security.yaml
security:
# ...
firewalls:
main:
# ...
switch_user: { role: CAN_SWITCH_USER }
.. code-block:: xml
.. code-block:: php
// config/packages/security.php
$container->loadFromExtension('security', [
// ...
'firewalls' => [
'main'=> [
// ...
'switch_user' => [
'role' => 'CAN_SWITCH_USER',
],
],
],
]);
Then, create a voter class that responds to this role and includes whatever custom
logic you want::
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class SwitchToCustomerVoter extends Voter
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
protected function supports($attribute, $subject)
{
return in_array($attribute, ['CAN_SWITCH_USER'])
&& $subject instanceof UserInterface;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
// if the user is anonymous or if the subject is not a user, do not grant access
if (!$user instanceof UserInterface || !$subject instanceof UserInterface) {
return false;
}
// you can still check for ROLE_ALLOWED_TO_SWITCH
if ($this->security->isGranted('ROLE_ALLOWED_TO_SWITCH')) {
return true;
}
// check for any roles you want
if ($this->security->isGranted('ROLE_TECH_SUPPORT')) {
return true;
}
/*
* or use some custom data from your User object
if ($user->isAllowedToSwitch()) {
return true;
}
*/
return false;
}
}
That's it! When switching users, your voter now has full control over whether or
not this is allowed. If your voter isn't called, see :ref:`declaring-the-voter-as-a-service`.
Events
------
The firewall dispatches the ``security.switch_user`` event right after the impersonation
is completed. The :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent` is
passed to the listener, and you can use this to get the user that you are now impersonating.
The :doc:`/session/locale_sticky_session` article does not update the locale
when you impersonate a user. If you *do* want to be sure to update the locale when
you switch users, add an event subscriber on this event::
// src/EventListener/SwitchUserSubscriber.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;
class SwitchUserSubscriber implements EventSubscriberInterface
{
public function onSwitchUser(SwitchUserEvent $event)
{
$request = $event->getRequest();
if ($request->hasSession() && ($session = $request->getSession())) {
$session->set(
'_locale',
// assuming your User has some getLocale() method
$event->getTargetUser()->getLocale()
);
}
}
public static function getSubscribedEvents()
{
return [
// constant for security.switch_user
SecurityEvents::SWITCH_USER => 'onSwitchUser',
];
}
}
That's it! If you're using the :ref:`default services.yaml configuration `,
Symfony will automatically discover your service and call ``onSwitchUser`` whenever
a switch user occurs.
For more details about event subscribers, see :doc:`/event_dispatcher`.