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!

  • 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?