Buy

The UserProvider: Custom Logic to Load Security Users

UPGRADE! Check out the newest version of this tutorial

The UserProvider: Custom Logic to Load Security Users

Hey there repository expert. So our actual goal was to let the user login using a username or email. If we could get the security system to use our shiny new findOneByUsernameOrEmail method to look up users at login, we’d be done. And back to our real job of crushing the rebel forces.

Open up security.yml and remove the property key from our entity provider:

# app/config/security.yml
security:
    # ...

    providers:
        our_database_users:
            entity: { class: UserBundle:User }

Try logging in now! Ah, a great error:

The Doctrine repository “Yoda\UserBundle\Entity\UserRepository” must implement UserProviderInterface.

The UserProviderInterface

Without the property, Doctrine has no idea how to look up the User. Instead it tries to call a method on our UserRepository. But for that to work, our UserRepository class must implement UserProviderInterface.

So let’s open up UserRepository and make this happen:

// src/Yoda/UserBundle/Entity/UserRepository.php
// ...

use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserRepository extends EntityRepository implements UserProviderInterface
{
    // ...
}

As always, don’t forget your use statement! This interface requires 3 methods: refreshUser, supportsClass and loadUserByUsername. I’ll just paste these in:

// src/Yoda/UserBundle/Entity/UserRepository.php
// ...

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;

class UserRepository extends EntityRepository implements UserProviderInterface
{
    // ...

    public function loadUserByUsername($username)
    {
        // todo
    }

    public function refreshUser(UserInterface $user)
    {
        $class = get_class($user);
        if (!$this->supportsClass($class)) {
            throw new UnsupportedUserException(sprintf(
                'Instances of "%s" are not supported.',
                $class
            ));
        }

        if (!$refreshedUser = $this->find($user->getId())) {
            throw new UsernameNotFoundException(sprintf('User with id %s not found', json_encode($user->getId())));
        }

        return $refreshedUser;
    }

    public function supportsClass($class)
    {
        return $this->getEntityName() === $class
            || is_subclass_of($class, $this->getEntityName());
    }
}

Tip

You can get this code from the resources directory of the code download.

Filling in loadUserByUsername

The really important method is loadUserByUsername because Symfony calls it when you login to get the User object for the given username. So we can use any logic we want to find or not find a user, like never returning User’s named “Jar Jar Binks”:

public function loadUserByUsername($username)
{
    if ($username == 'jarjarbinks') {
        // nope!
        return;
    }
}

We can just re-use the findOneByUsernameOrEmail method we created earlier. If no user is found, this method should throw a special UsernameNotFoundException:

// src/Yoda/UserBundle/Entity/UserRepository.php
// ...

class UserRepository extends EntityRepository implements UserProviderInterface
{
    // ...

    public function loadUserByUsername($username)
    {
        $user = $this->findOneByUsernameOrEmail($username);

        if (!$user) {
            throw new UsernameNotFoundException('No user found for username '.$username);
        }

        return $user;
    }

    // ... refreshUser and supportsClass from above...
}

Try logging in again using the email address. It works! Behind the scenes, Symfony calls the loadUserByUsername method and passes in the username we submitted. We return the right User object and then the authentication just keeps going like normal. We don’t have to worry about checking the password because Symfony still does that for us.

Ok, enough about security and Doctrine! But give yourself a high-five because you just learned some of the most powerful, but difficult stuff when using Symfony and Doctrine. You now have an elegant form login system that loads users from the database and that gives you a lot of control over exactly how those users are loaded.

Now for a registration page!

Leave a comment!

  • 2017-01-13 weaverryan

    Hey Carlos!

    Originally, people used the user provider inside of their authentication system in order to load their users. In fact, for many of the built-in authentication mechanisms, like form_login, http_basic, etc, this is still true: you submit a username + password form, and the form_login authentication mechanism uses your user provider (e.g. the entity provider) to know where to load that User. So, if you're using one of these mechanisms, and you need to load your users from a different location (perhaps your User information is stored across an API), you'll need a custom user provider.

    However, thanks to Guard, I recommend now that people use it to create their own authentication mechanisms whenever they have any non-standard situation (a login form that loads from the database is standard, so using form_login is fine). In Guard, *you* are in complete control of loading your User object however you want (in your getUser() method), and I recommend for clarity, *not* using your user provider to do this work. Instead, for example, if you need to load User information from some API, then just write the code in your authenticator and do it! In this situation, when would you need a custom user provider? Well, remember, a user provider does basically 2 things:

    A) It refreshes the User from the session (UserProviderInterface::refreshUser(UserInterface $user)
    B) It loads a user by username for remember_me and switch_user (UserProviderInterface::loadUserByUsername($username))

    So, you'll need a custom user provider basically if your User information is *not* stored in a database. If it is, just use "entity". If you *do* originally load User information from some other place (e.g. an API) but then save a new User database record for each user, then you should *still* use the entity provider. A custom provider is needed only if your User object can only be populated by loading data from some custom (i.e. non-database) source.

    So, in theory, it's not SO uncommon... but I rarely see it: almost always, people are loading users from a database. Even if they have some big central-authentication system, they often store a local User object in their database so that they can store application-specific information about that User. And as I mentioned, if this is the case, you should still use the entity provider.

    I hope this helps! The user provider is unfortunately confusing in Symfony, which is why I try to minimize how much people worry about it. In 99% of the use-cases, you should configure the "entity" provider in security.yml and never think of it again :).

    Cheers!

  • 2017-01-12 Carlos

    Ah, I understood.
    We use the Symfony default user provider. Indicated in security.yml.

    Could you tell me a case where a custom user provider really needs it?
    Thanks Ryan

  • 2017-01-12 weaverryan

    Hey Carlos!

    Oh boy, the user provider is very confusing. First, you don't implement UserProviderInterface in the User entity - the User entity implements UserInterface. So, the User and your "user provider" are 2 separate things. But, what you said about the UserProvider is true: it allows you to use remember me functionality, save user to the session and do impersonation. And, in practice, all authentication systems *must* have a User Provider. So, it's a necessary part of your system, but many people don't even realize it exists or what it does. If you're using Doctrine and your User class is an entity, then you've probably configured the "entity" user provider in security.yml under the "providers" key (http://symfony.com/doc/current.... Doing that is enough to configure a user provider that does all of the things you mentioned. I think it should be quite rare that you need a custom user provider.

    I hope this helps! It *is* confusing!

  • 2017-01-10 Carlos

    Hello, I have a question about the user provider.

    Is it mandatory to implement the UserProviderInterface class?
    http://symfony.com/doc/current...

    As I understand it, if we implement UserProviderInterface in User Entity, we can make use of remember me functionality, save user in session, (this part, currently in the application does not perform it?) And perform impersonation users (currently, we do it and not We implemented UserProviderInterface and we use this).

    I have a little of trouble with this ...

    Thanks for your job

  • 2016-12-21 weaverryan

    Woohoo! You just tackled what I think is the hardest part of Symfony's security component (and maybe the most confusing part of Symfony in general). Congrats - and thanks for following up :)

  • 2016-12-21 Paul-André Duchesne

    Hi Ryan,

    First, sorry for my late answer.
    A great thanks for what you suggested, it's working fine...

    After having tried what you suggested, I've just chosen, at least, to keep the injection of the LdapUserProvider in my constructor to be able to use it in the instanciation of a new LdapBindAuthentication in the constructor self (used later on for ldapBindAuth->verifyAuthentication()). That facilitates also the call later on to the loadByUsername method (instead of copying it inside the authenticator and trying to get the necessary parameters used inside that method).

    I understand now correctly the distingo between the role of the "User Provider" for refreshing and passing the user at the beginning of each request and the effective injection of an other "ldap user provider" used for convenience later on...
    Without the ChainProvider existence, the things are cleaner and more understandable.

    Thanks again a lot, your courses and explanations are awesome !

    Kind regards,

    Paul-André

  • 2016-12-14 weaverryan

    Hey Paul-André!

    Awesome! This is exactly the deep description I wanted! So, let me repeat your setup, in a slightly different way. No matter *how* the user is found or their password is checked, ultimately, all users are stored in the database. This means that you only have one User class (this is good) and that User class is an entity. This also means that, once a user is logged in, I can say with 100% certainty that I can find this user in our database, by using their username.

    So, this makes life GREAT for your UserProvider. Here's what I'd like you to do:

    A) Read / watch this page again (https://knpuniversity.com/scre... just to really focus on the job of these user providers.

    B) Delete the ldap_users user provider and corresponding service entirely. I know you're injecting the LdapUserProvider into your authenticator to help with your work there, but to simplify things, stop doing that. Instead, implement the LDAP lookup logic yourself in the authenticator. This could mean copying this method into your authenticator: https://github.com/symfony/sym.... So, you'll now inject the Ldap class/service into your authenticator instead. Technically, you *could* still use the LdapUserProvider once we're done... but stop using it for now - I want to simplify what these "user provider" things are and aren't for.

    C) At this point, you will not be using *any* user provider inside of your authenticator. You'll simply be using the Ldap and EntityManager objects directly. Nice and simple.

    D) You will now only want *one* user provider in security.yml, and it will be your entity user provider. So, keep only that - and remove all the chain stuff.
    That should do it. Summary of this is:

    1) In your authenticator, forget that these "user provider" things exist. Just use Ldap and EntityManager to do your job. I can tell by your comments that you already understand the logic that should live in this class well.

    2) The *only* purpose of the "user provider" will be to "refresh" the user at the beginning of each request. Your User object is stored in the session. Then, on the next page, it's deserialized. The "entity" user provider will then use the primary key that User to re-query for a fresh one (to make sure its data is not stale). Since all our users are stored in the database, that's perfect! Ldap will not be involved in this part of the process at all.

    Let me know how it goes!

  • 2016-12-13 Paul-André Duchesne

    Hi Ryan,

    First, Great, Great thanks for having me lead in the right direction... with the LdapBindAuthenticationProvider. I've extended that class in one of my own and adapted the things a bit to mimic the checkAuthentication method.
    The main difficulties I encountered were at last with our AD authentication itself ;)

    So, to help you helping me with the chain provider, here are answers to the list of questions:
    * All the users of the application are stored in the database - our AD has got only the people who are still considered in activity in our institution - the AD has got thus only a subset of what is stored in the database. So, in the getUser method of my authenticator, I used only the EntityManager to get the corresponding user out of database.
    * As the AD is responsible of authenticating the "active" users and as the users can modify their password through their windows desktop, my intention (and what I've done) was:
    * if a user is authenticated against the AD, that meant his/her password was recognized... and that's ok... so in the checkCredentials method of my authenticator, I putted the call to the adapted checkAuthentication method in a try (as you suggested), and if it succeed, I return true.
    * if a user is not authenticated against the AD, I try to find him/her in the AD with the loadUserByUsername method of the ldap client. If (s)he's found, I return false meaning the username was right but password not and if (s)he's not found, I check if password is valid against the database. If yes, the user is logged in, otherwise, it sends back false (and thus invalid credentials)

    Concerning the update of password in the database, if the user succeed to login against the AD, I update (and encrypt) the password field of the user in the database.

    So the moment I'm extending the LdapBindAuthenticatorProvider who needs a UserProviderInterface and the moment I'm playing with a user coming from database, I guess I need both of them in a ChainProvider... Am I right ?

    Thanks again a hundred time for the help provided Ryan,

    Kind regards,

    Paul-André

  • 2016-12-10 weaverryan

    Hi Paul-André!

    Unfortunately, I don't know a lot personally about LDAP. But, I do know a lot about Guard, so I'll do my best to help :).

    1) Since you're using Guard, obviously you need to do a little bit more work yourself in the authenticator (which is ok, because you also get a lot more flexibility). But, we can use the built-in LDAP authentication classes as inspiration for what we do. For example, the form_login_ldap authentication listener, is actually this class: https://github.com/symfony/sym.... So, how should you check the users password? Mimic was it's doing here: https://github.com/symfony/sym.... Specifically, the $this->ldap library is your app.security.ldap service. So this is what you should inject into your authenticator.

    2) And yes, I noticed your "chain" user provider. User providers can be complex to understand, and we definitely have a problem here. First, make sure you review this: http://knpuniversity.com/scree... - it tries to explain the purpose of the user provider. Your exact correct setup depends on your needs. So, I'm going to ask a *bunch* of questions:

    As I understand it, your users should be able to login via their LDAP credentials or by logging in with their username/password that is stored in the database. Is this correct? Do you also have 2 different user classes? And, will you need to store application information about all users, or is the purpose for the users ONLY to be able to login? Will some users *only* exist in the database, but not in LDAP? And will some where *only* exist in LDAP and not in our local database? If a user exists in the database, but not in LDAP, will they thus manage their password in your system (instead of LDAP)? And if they exist in LDAP and not in your local database, I assume they will thus manage their password in LDAP and not your system? Or asked differently, is it true that some users will be logging in with their password in LDAP and other users will be logging in with their password that's manage by your system?

    Pass me some more information and I should be able to help your get your providers worked out correctly! But remember this point about providers:

    A) The point of a user provider is *only* to reload the user (e.g. from the database) at the beginning of each request. So, if ultimately - once the user has logged in - you have a local "user" record in your database for every user, then you *only* need the entity provider. Otherwise, yes, you'll need a chain provider like you have.

    B) When the user is originally logging in, you are in 100% full control of how that works - via your getUser() method. You could absolutely first look for the user in LDAP (you'll need a try-catch around loadUserByUsername, since this throws a UsernameNotFoundException) and if it's not found, look for it in your database.

    Cheers!

  • 2016-12-09 Paul-André Duchesne

    Hello Ryan,

    I come towards you because I'm stuck with the implementation of authenticating against an ldap server with guard.
    I've followed a bit of this: http://symfony.com/doc/current... - adapted a bit due to a bit outdated version of the documentation and I've got this:
    In services.yml:

    app.security.ldap.adapter:
    class: Symfony\Component\Ldap\Adapter\ExtLdap\Adapter
    arguments:
    - host: "%ldap_server%"
    autowire: true
    app.security.ldap:
    class: Symfony\Component\Ldap\Ldap
    arguments: [ "@app.security.ldap.adapter" ]
    autowire: true
    app.security.login_form_ldap_authenticator:
    class: AppBundle\Security\LoginFormLdapAuthenticator
    autowire: true


    In security.yml:

    security:

    encoders:
    AppBundle\Entity\Person: bcrypt

    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    providers:
    chain_provider:
    chain:
    providers:
    - ldap_users
    - db_users
    db_users:
    entity: { class: AppBundle\Entity\Person, property: uid }
    ldap_users:
    ldap:
    service: app.security.ldap
    base_dn: cn=Users,dc=rbins,dc=be
    search_dn: "cn=%ldap_user%,ou=%ldap_ou%,dc=rbins,dc=be"
    search_password: "%ldap_pwd%"
    default_roles: [ROLE_USER]
    filter: (&(ObjectClass=Person)({uid_key}={username}))
    firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
    pattern: ^/(_(profiler|wdt)|css|images|js)/
    security: false

    rest:
    pattern: ^/rest/
    anonymous: true
    stateless: true
    security: false

    main:
    anonymous: ~
    guard:
    authenticators:
    - app.security.login_form_ldap_authenticator
    - app.security.login_form_authenticator
    entry_point: app.security.login_form_ldap_authenticator


    In the AppBundle\Security\LoginFormLdapAuthenticator in the constructor, I've been well defined a Symfony\Component\Security\Core\User\LdapUserProvider that I set in a $this->ldapUser variable that I use in the getUser method to check existence of the username in the Ldap...
    'Till that point everything's fine...
    But in the checkCredentials, I don't know what (and how) to call (and to inject in my constructor) to authenticate the user against the ldap server...

    Could you suggest me any direction that could help me going further ?

    Kind regards,

    Paul-André

    PS.: You've certainly seen that I've defined a chain provider and two authenticators. My expectations were that if the first fails, it tries to fallback on the second one... But it didn't appeared to act so. For instance, if the user wasn't found in the ldap, it stopped and I got back to the login screen with the corresponding 'user unknown' exception displayed... and I've seen that the UserProvider parameter in the getUser was a ChainProvider containing the two others... What could I do to expect fall back on the second provider ? Should I only make one authenticator and handle the fallback in each methods ?

  • 2016-08-18 weaverryan

    Sweet! Catching your own errors? No better way to learn something :). Cheers!

  • 2016-08-18 james

    Yes found it just a few minutes ago, I forgot some private fields in LoginFormAuthenticator... My bad!

    Thanks anyway for the lightning fast answer!

  • 2016-08-18 weaverryan

    Hi James!

    That's really interesting! It *appears* that you logged in... but somehow are missing your User class. There's probably some tiny issue somewhere. Let's check out a few things:

    1) What does the getUsername() method look like in your User class? If there is some bug in this, and it's returning a blank string, then I *think* it might cause this problem :).

    2) If you hover over the icon in the web debug toolbar, what does it say for "Token class"? What about "Logged in as" - is that just blank?

    3) If you turn intercept_redirects temporarily back to *true* and login, does the web debug toolbar look any different before you actually redirect?

    4) After logging in, go into your favorite controller and run this code:


    dump($this->get('security.token_storage')->getToken());die;

    Basically, you're getting logged in, but *something* is wrong with the User class, which is inside of this "Token" class. The above dump would tell us more info about what's going on.

    Let me know what you find out!

  • 2016-08-18 james

    Following everything and I have just something different as a result, the user username does not appear in the dev bar like you. It is just n/a; the authentification went through and I am logged in. What could be the cause?

    Thanks

  • 2016-06-29 weaverryan

    Nice job! It's actually quite different than the *old* "User Provider" chapter we had for Symfony2. Not a lot of has technically changed, but the way I like to explain it has :).

    Cheers!

  • 2016-06-29 JLChafardet

    isnt this a recycled vid!! lol well i caught up! hope the next batch of vids comes soon

  • 2016-04-05 Diego Aguiar

    No problem, I hope someone find this useful

  • 2016-04-01 weaverryan

    Yes, good find! We changed that in Symfony 2.8 - the new interface is a little bit easier than the old one. DX improvement... as long as you know it exists :). Thanks for posting your solution!

  • 2016-03-31 Diego Aguiar

    I found it, in symfony3 no longer requires to implement "UserProviderInterface" instead it needs to implement "UserLoaderInterface"

  • 2016-03-30 Diego Aguiar

    Hi Ryan,
    looks like something has changed in symfony3, I've done everything thru this course and I'm always getting this error **Authentication request could not be processed due to a system problem.** when I try to login after doing the user provider change
    I just can't figure it out how to fix it, any help would be very appreciated

  • 2015-11-03 Medoune

    Hello, I works! It was due to the isAccountNonLocked() which was set to false!

    Thanks

  • 2015-11-02 weaverryan

    Hi there!

    This message can only be displayed because of 1 reason: your User class implements AdvancedUserInterface and the isAccountNonLocked() method returns false. Make sure this returns *true* for the User. I know, it has a confusing name "non-locked" is kind of a double negative. Anyways, here's the spot in the core that calls this method and gives you that error: https://github.com/symfony/sym...

    Cheers!

  • 2015-11-01 Medoune

    Hello,
    I have done everything for this lesson, and everything works well but when I try to log in with a username who is active, I get "Account is locked"! And I can't found what that issues is from?

    Sorry for my bad English

  • 2015-10-12 weaverryan

    Hey Léo!

    Ah, I missed this message! And it's a really good question. The reason is because of this "hide_user_not_found" option under security: http://symfony.com/doc/current.... This makes it so that when we throw a UsernameNotFoundException, it turns it into a different exception. Here's the code for that: https://github.com/symfony/sym...

    There's a bit more behind the scenes to controlling the exception messages in Symfony, and it's something that's been improved in Symfony 2.8 so that you have more control via a class called CustomUserMessageAuthenticationException: https://github.com/symfony/sym....

    Cheers!

  • 2015-09-16 Léo Li

    Hello again,

    For this lesson, I've done everything, and it works well for login by username or by email. But, when I enter a username that doesn't exist, I still get an error "Wrong password bro!". But I think it should be "No user found for XXXX", right?