How to Login with a username *or* email (or crazier)

Whenever you login, you identify yourself. For a form, this might be with a username or email. With an API, the token often serves both to identify who you are and serve as a sort of "password".

With Guard, you can use any crazy combination of methods to figure out who is trying to authenticate. The only rule is that your getUser function returns some object that implements UserInterface.

Let's look at an example of how you could customize this:

Logging in with a username or email

In the Form Login chapter, we built a login form that queries for a user from the database using the username property:

68 lines src/AppBundle/Security/FormLoginAuthenticator.php
... lines 1 - 11
use Symfony\Component\Security\Core\User\UserProviderInterface;
... line 13
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 38
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['username'];
return $userProvider->loadUserByUsername($username);
}
... lines 45 - 66
}

But what if we wanted to let the user enter his username or email? First, create a method inside your UserRepository for this query:

41 lines src/AppBundle/Repository/UserRepository.php
... lines 1 - 25
class UserRepository extends EntityRepository
{
/**
* @param string $username
* @return User
*/
public function findByUsernameOrEmail($username)
{
return $this->createQueryBuilder('u')
->andWhere('u.username = :username OR u.email = :username')
->setParameter('username', $username)
->getQuery()
->getOneOrNullResult();
}
}

Now, in getUser(), simply call this method to return your User object:

72 lines src/AppBundle/Security/FormLoginAuthenticator.php
... lines 1 - 13
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 38
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['username'];
$userRepo = $this->container
->get('doctrine')
->getManager()
->getRepository('AppBundle:User');
return $userRepo->findByUsernameOrEmail($username);
}
... lines 49 - 70
}

This works because we're injecting the entire service container. But, you could just as easily inject only the entity manager to clean things up.

Now, wasn't that easy? Have some other weird requirement for how a user is loaded? Do whatever you want inside of getUser().

Tip

Why not use the $userProvider argument? The $userProvider that's passed to us here is what we have configured in security.yml under the providers key. In this project, this object gives us a loadUserByUsername method that queries for the User by the username. We could customize the user provider and make it do what we want. Or, we could simply fetch our repository directly and query for what we need. That seems much easier, and I've yet to see a downside.

Leave a comment!

  • 2016-05-11 weaverryan

    Boom! Awesome - good find :)

  • 2016-05-11 Yoni L.

    Hi, thanks for this, it works like a charm! I tried to inject only the entity manager , if it works great with getUser, how can I get the router service without the container for getLoginUrl and getDefaultSuccessRedirectUrl method?
    Thx
    __Edit__: found the solution while watching your csrf protection commit:
    https://github.com/knpuniversi...
    simply pass the router as parameter in the constructor