Security User Providers

Security User Providers

User providers are PHP classes related to Symfony Security that have two jobs:

Reload the User from the Session

At the beginning of each request (unless your firewall is stateless), Symfony loads the User object from the session. To make sure it’s not out-of-date, the user provider “refreshes it”. The Doctrine user provider, for example, queries the database for fresh data. Symfony then checks to see if the user has “changed” and de-authenticates the user if they have (see Understanding how Users are Refreshed from the Session).

Load the User for some Feature

Some features, like user impersonation, Remember Me and many of the built-in authentication providers, use the user provider to load a User object via its “username” (or email, or whatever field you want).

Symfony comes with several built-in user providers:

The built-in user providers cover all the needs for most applications, but you can also create your own custom user provider.

Entity User Provider

This is the most common user provider for traditional web applications. Users are stored in a database and the user provider uses Doctrine to retrieve them:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. providers:
    5. users:
    6. entity:
    7. # the class of the entity that represents users
    8. class: 'App\Entity\User'
    9. # the property to query by - e.g. username, email, etc
    10. property: 'username'
    11. # optional: if you're using multiple Doctrine entity
    12. # managers, this option defines which one to use
    13. # manager_name: 'customer'
    14. # ...
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd">
    8. <config>
    9. <provider name="users">
    10. <!-- 'class' is the entity that represents users and 'property'
    11. is the entity property to query by - e.g. username, email, etc -->
    12. <entity class="App\Entity\User" property="username"/>
    13. <!-- optional: if you're using multiple Doctrine entity
    14. managers, this option defines which one to use -->
    15. <!-- <entity class="App\Entity\User" property="username"
    16. manager-name="customer"/> -->
    17. </provider>
    18. <!-- ... -->
    19. </config>
    20. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Entity\User;
    3. $container->loadFromExtension('security', [
    4. 'providers' => [
    5. 'users' => [
    6. 'entity' => [
    7. // the class of the entity that represents users
    8. 'class' => User::class,
    9. // the property to query by - e.g. username, email, etc
    10. 'property' => 'username',
    11. // optional: if you're using multiple Doctrine entity
    12. // managers, this option defines which one to use
    13. // 'manager_name' => 'customer',
    14. ],
    15. ],
    16. ],
    17. // ...
    18. ]);

The providers section creates a “user provider” called users that knows how to query from your App\Entity\User entity by the username property. You can choose any name for the user provider, but it’s recommended to pick a descriptive name because this will be later used in the firewall configuration.

Using a Custom Query to Load the User

The entity provider can only query from one specific field, specified by the property config key. If you want a bit more control over this - e.g. you want to find a user by email or username, you can do that by making your UserRepository implement the Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface. This interface only requires one method: loadUserByUsername($username):

  1. // src/Repository/UserRepository.php
  2. namespace App\Repository;
  3. use App\Entity\User;
  4. use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
  5. use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
  6. class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
  7. {
  8. // ...
  9. public function loadUserByUsername($usernameOrEmail): ?User
  10. {
  11. $entityManager = $this->getEntityManager();
  12. return $entityManager->createQuery(
  13. 'SELECT u
  14. FROM App\Entity\User u
  15. WHERE u.username = :query
  16. OR u.email = :query'
  17. )
  18. ->setParameter('query', $usernameOrEmail)
  19. ->getOneOrNullResult();
  20. }
  21. }

To finish this, remove the property key from the user provider in security.yaml:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. providers:
    5. users:
    6. entity:
    7. class: App\Entity\User
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd">
    8. <config>
    9. <!-- ... -->
    10. <provider name="users">
    11. <entity class="App\Entity\User"/>
    12. </provider>
    13. </config>
    14. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Entity\User;
    3. $container->loadFromExtension('security', [
    4. // ...
    5. 'providers' => [
    6. 'users' => [
    7. 'entity' => [
    8. 'class' => User::class,
    9. ],
    10. ],
    11. ],
    12. ]);

This tells Symfony to not query automatically for the User. Instead, when needed (e.g. because user impersonation, Remember Me, or some other security feature is activated), the loadUserByUsername() method on UserRepository will be called.

Memory User Provider

It’s not recommended to use this provider in real applications because of its limitations and how difficult it is to manage users. It may be useful in application prototypes and for limited applications that don’t store users in databases.

This user provider stores all user information in a configuration file, including their passwords. That’s why the first step is to configure how these users will encode their passwords:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. encoders:
    5. # this internal class is used by Symfony to represent in-memory users
    6. Symfony\Component\Security\Core\User\User: 'auto'
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd"
    8. >
    9. <config>
    10. <!-- ... -->
    11. <!-- this internal class is used by Symfony to represent in-memory users -->
    12. <encoder class="Symfony\Component\Security\Core\User\User"
    13. algorithm="auto"
    14. />
    15. </config>
    16. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. // this internal class is used by Symfony to represent in-memory users
    3. use Symfony\Component\Security\Core\User\User;
    4. $container->loadFromExtension('security', [
    5. // ...
    6. 'encoders' => [
    7. User::class => [
    8. 'algorithm' => 'auto',
    9. ],
    10. ],
    11. ]);

Then, run this command to encode the plain text passwords of your users:

  1. $ php bin/console security:encode-password

Now you can configure all the user information in config/packages/security.yaml:

  1. # config/packages/security.yaml
  2. security:
  3. # ...
  4. providers:
  5. backend_users:
  6. memory:
  7. users:
  8. john_admin: { password: '$2y$13$jxGxc ... IuqDju', roles: ['ROLE_ADMIN'] }
  9. jane_admin: { password: '$2y$13$PFi1I ... rGwXCZ', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }

Caution

When using a memory provider, and not the auto algorithm, you have to choose an encoding without salt (i.e. bcrypt).

LDAP User Provider

This user provider requires installing certain dependencies and using some special authentication providers, so it’s explained in a separate article: Authenticating against an LDAP server.

Chain User Provider

This user provider combines two or more of the other provider types (entity, memory and ldap) to create a new user provider. The order in which providers are configured is important because Symfony will look for users starting from the first provider and will keep looking for in the other providers until the user is found:

  1. # config/packages/security.yaml
  2. security:
  3. # ...
  4. providers:
  5. backend_users:
  6. memory:
  7. # ...
  8. legacy_users:
  9. entity:
  10. # ...
  11. users:
  12. entity:
  13. # ...
  14. all_users:
  15. chain:
  16. providers: ['legacy_users', 'users', 'backend_users']

Creating a Custom User Provider

Most applications don’t need to create a custom provider. If you store users in a database, a LDAP server or a configuration file, Symfony supports that. However, if you’re loading users from a custom location (e.g. via an API or legacy database connection), you’ll need to create a custom user provider.

First, make sure you’ve followed the Security Guide to create your User class.

If you used the make:user command to create your User class (and you answered the questions indicating that you need a custom user provider), that command will generate a nice skeleton to get you started:

  1. // src/Security/UserProvider.php
  2. namespace App\Security;
  3. use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
  4. use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  5. use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
  6. use Symfony\Component\Security\Core\User\UserInterface;
  7. use Symfony\Component\Security\Core\User\UserProviderInterface;
  8. class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
  9. {
  10. /**
  11. * Symfony calls this method if you use features like switch_user
  12. * or remember_me.
  13. *
  14. * If you're not using these features, you do not need to implement
  15. * this method.
  16. *
  17. * @return UserInterface
  18. *
  19. * @throws UsernameNotFoundException if the user is not found
  20. */
  21. public function loadUserByUsername($username)
  22. {
  23. // Load a User object from your data source or throw UsernameNotFoundException.
  24. // The $username argument may not actually be a username:
  25. // it is whatever value is being returned by the getUsername()
  26. // method in your User class.
  27. throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
  28. }
  29. /**
  30. * Refreshes the user after being reloaded from the session.
  31. *
  32. * When a user is logged in, at the beginning of each request, the
  33. * User object is loaded from the session and then this method is
  34. * called. Your job is to make sure the user's data is still fresh by,
  35. * for example, re-querying for fresh User data.
  36. *
  37. * If your firewall is "stateless: true" (for a pure API), this
  38. * method is not called.
  39. *
  40. * @return UserInterface
  41. */
  42. public function refreshUser(UserInterface $user)
  43. {
  44. if (!$user instanceof User) {
  45. throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
  46. }
  47. // Return a User object after making sure its data is "fresh".
  48. // Or throw a UsernameNotFoundException if the user no longer exists.
  49. throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
  50. }
  51. /**
  52. * Tells Symfony to use this provider for this User class.
  53. */
  54. public function supportsClass($class)
  55. {
  56. return User::class === $class || is_subclass_of($class, User::class);
  57. }
  58. /**
  59. * Upgrades the encoded password of a user, typically for using a better hash algorithm.
  60. */
  61. public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
  62. {
  63. // TODO: when encoded passwords are in use, this method should:
  64. // 1. persist the new password in the user storage
  65. // 2. update the $user object with $user->setPassword($newEncodedPassword);
  66. }
  67. }

Most of the work is already done! Read the comments in the code and update the TODO sections to finish the user provider. When you’re done, tell Symfony about the user provider by adding it in security.yaml:

  1. # config/packages/security.yaml
  2. security:
  3. providers:
  4. # the name of your user provider can be anything
  5. your_custom_user_provider:
  6. id: App\Security\UserProvider

Lastly, update the config/packages/security.yaml file to set the provider key to your_custom_user_provider in all the firewalls which will use this custom user provider.

Understanding how Users are Refreshed from the Session

At the end of every request (unless your firewall is stateless), your User object is serialized to the session. At the beginning of the next request, it’s deserialized and then passed to your user provider to “refresh” it (e.g. Doctrine queries for a fresh user).

Then, the two User objects (the original from the session and the refreshed User object) are “compared” to see if they are “equal”. By default, the core AbstractToken class compares the return values of the getPassword(), getSalt() and getUsername() methods. If any of these are different, your user will be logged out. This is a security measure to make sure that malicious users can be de-authenticated if core user data changes.

However, in some cases, this process can cause unexpected authentication problems. If you’re having problems authenticating, it could be that you are authenticating successfully, but you immediately lose authentication after the first redirect.

In that case, review the serialization logic (e.g. SerializableInterface) if you have any, to make sure that all the fields necessary are serialized.

Comparing Users Manually with EquatableInterface

Or, if you need more control over the “compare users” process, make your User class implement Symfony\Component\Security\Core\User\EquatableInterface. Then, your isEqualTo() method will be called when comparing users.

Injecting a User Provider in your Services

Symfony defines several services related to user providers:

  1. $ php bin/console debug:container user.provider
  2. Select one of the following services to display its information:
  3. [0] security.user.provider.in_memory
  4. [1] security.user.provider.ldap
  5. [2] security.user.provider.chain
  6. ...

Most of these services are abstract and cannot be injected in your services. Instead, you must inject the normal service that Symfony creates for each of your user providers. The names of these services follow this pattern: security.user.provider.concrete.<your-provider-name>.

For example, if you are building a form login and want to inject in your LoginFormAuthenticator a user provider of type memory and called backend_users, do the following:

  1. // src/Security/LoginFormAuthenticator.php
  2. namespace App\Security;
  3. use Symfony\Component\Security\Core\User\InMemoryUserProvider;
  4. use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
  5. class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
  6. {
  7. private $userProvider;
  8. // change the 'InMemoryUserProvider' type-hint in the constructor if
  9. // you are injecting a different type of user provider
  10. public function __construct(InMemoryUserProvider $userProvider, /* ... */)
  11. {
  12. $this->userProvider = $userProvider;
  13. // ...
  14. }
  15. }

Then, inject the concrete service created by Symfony for the backend_users user provider:

  1. # config/services.yaml
  2. services:
  3. # ...
  4. App\Security\LoginFormAuthenticator:
  5. $userProvider: '@security.user.provider.concrete.backend_users'

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.