.. index:: single: Security; User Provider How to create a custom User Provider ==================================== Part of Symfony's standard authentication process depends on "user providers". When a user submits a username and password, the authentication layer asks the configured user provider to return a user object for a given username. Symfony then checks whether the password of this user is correct and generates a security token so the user stays authenticated during the current session. Out of the box, Symfony has an "in_memory" and an "entity" user provider. In this entry you'll see how you can create your own user provider, which could be useful if your users are accessed via a custom database, a file, or - as shown in this example - a web service. Create a User Class ------------------- First, regardless of *where* your user data is coming from, you'll need to create a ``User`` class that represents that data. The ``User`` can look however you want and contain any data. The only requirement is that the class implements :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. The methods in this interface should therefore be defined in the custom user class: :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getRoles`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getPassword`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getSalt`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getUsername`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::eraseCredentials`. It may also be useful to implement the :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface` interface, which defines a method to check if the user is equal to the current user. This interface requires an :method:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface::isEqualTo` method. Let's see this in action:: // src/Acme/WebserviceUserBundle/Security/User/WebserviceUser.php namespace Acme\WebserviceUserBundle\Security\User; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\EquatableInterface; class WebserviceUser implements UserInterface, EquatableInterface { private $username; private $password; private $salt; private $roles; public function __construct($username, $password, $salt, array $roles) { $this->username = $username; $this->password = $password; $this->salt = $salt; $this->roles = $roles; } public function getRoles() { return $this->roles; } public function getPassword() { return $this->password; } public function getSalt() { return $this->salt; } public function getUsername() { return $this->username; } public function eraseCredentials() { } public function isEqualTo(UserInterface $user) { if (!$user instanceof WebserviceUser) { return false; } if ($this->password !== $user->getPassword()) { return false; } if ($this->getSalt() !== $user->getSalt()) { return false; } if ($this->username !== $user->getUsername()) { return false; } return true; } } If you have more information about your users - like a "first name" - then you can add a ``firstName`` field to hold that data. Create a User Provider ---------------------- Now that you have a ``User`` class, you'll create a user provider, which will grab user information from some web service, create a ``WebserviceUser`` object, and populate it with data. The user provider is just a plain PHP class that has to implement the :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`, which requires three methods to be defined: ``loadUserByUsername($username)``, ``refreshUser(UserInterface $user)``, and ``supportsClass($class)``. For more details, see :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. Here's an example of how this might look:: // src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php namespace Acme\WebserviceUserBundle\Security\User; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; class WebserviceUserProvider implements UserProviderInterface { public function loadUserByUsername($username) { // make a call to your webservice here $userData = ... // pretend it returns an array on success, false if there is no user if ($userData) { $password = '...'; // ... return new WebserviceUser($username, $password, $salt, $roles); } throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); } public function refreshUser(UserInterface $user) { if (!$user instanceof WebserviceUser) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } return $this->loadUserByUsername($user->getUsername()); } public function supportsClass($class) { return $class === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser'; } } Create a Service for the User Provider -------------------------------------- Now you make the user provider available as a service: .. configuration-block:: .. code-block:: yaml # src/Acme/WebserviceUserBundle/Resources/config/services.yml parameters: webservice_user_provider.class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider services: webservice_user_provider: class: "%webservice_user_provider.class%" .. code-block:: xml Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider .. code-block:: php // src/Acme/WebserviceUserBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; $container->setParameter('webservice_user_provider.class', 'Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider'); $container->setDefinition('webservice_user_provider', new Definition('%webservice_user_provider.class%'); .. tip:: The real implementation of the user provider will probably have some dependencies or configuration options or other services. Add these as arguments in the service definition. .. note:: Make sure the services file is being imported. See :ref:`service-container-imports-directive` for details. Modify ``security.yml`` ----------------------- Everything comes together in your security configuration. Add the user provider to the list of providers in the "security" section. Choose a name for the user provider (e.g. "webservice") and mention the id of the service you just defined. .. configuration-block:: .. code-block:: yaml // app/config/security.yml security: providers: webservice: id: webservice_user_provider .. code-block:: xml .. code-block:: php // app/config/security.php $container->loadFromExtension('security', array( 'providers' => array( 'webservice' => array( 'id' => 'webservice_user_provider', ), ), )); Symfony also needs to know how to encode passwords that are supplied by website users, e.g. by filling in a login form. You can do this by adding a line to the "encoders" section in your security configuration: .. configuration-block:: .. code-block:: yaml # app/config/security.yml security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512 .. code-block:: xml sha512 .. code-block:: php // app/config/security.php $container->loadFromExtension('security', array( 'encoders' => array( 'Acme\WebserviceUserBundle\Security\User\WebserviceUser' => 'sha512', ), )); The value here should correspond with however the passwords were originally encoded when creating your users (however those users were created). When a user submits their password, the salt value is appended to the password and then encoded using this algorithm before being compared to the hashed password returned by your ``getPassword()`` method. Additionally, depending on your options, the password may be encoded multiple times and encoded to base64. .. sidebar:: Specifics on how passwords are encoded Symfony uses a specific method to combine the salt and encode the password before comparing it to your encoded password. If ``getSalt()`` returns nothing, then the submitted password is simply encoded using the algorithm you specify in ``security.yml``. If a salt *is* specified, then the following value is created and *then* hashed via the algorithm: ``$password.'{'.$salt.'}';`` If your external users have their passwords salted via a different method, then you'll need to do a bit more work so that Symfony properly encodes the password. That is beyond the scope of this entry, but would include sub-classing ``MessageDigestPasswordEncoder`` and overriding the ``mergePasswordAndSalt`` method. Additionally, the hash, by default, is encoded multiple times and encoded to base64. For specific details, see `MessageDigestPasswordEncoder`_. To prevent this, configure it in your configuration file: .. configuration-block:: .. code-block:: yaml # app/config/security.yml security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: algorithm: sha512 encode_as_base64: false iterations: 1 .. code-block:: xml .. code-block:: php // app/config/security.php $container->loadFromExtension('security', array( 'encoders' => array( 'Acme\WebserviceUserBundle\Security\User\WebserviceUser' => array( 'algorithm' => 'sha512', 'encode_as_base64' => false, 'iterations' => 1, ), ), )); .. _MessageDigestPasswordEncoder: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php