Search code examples
phpsymfonydoctrine-ormdoctrinesymfony-security

How to make sure case insensitive usernames are always accepted by Symfony UserBadge?


I have created a custom AbstractAuthenticator subclass in my Symfony 5 project which can process user credentials included in HTTP headers.

public function authenticate(Request $request): PassportInterface {
    $userIdentifier = $this->getUserIdentifierFromRequest();
    
    return new Passport(
        new UserBadge($this->userIdentifier),
        new CustomCredentials(
            function ($credentials, UserInterface $user) {
                ...
            },
            
            $credentialFromRequest
        )
    );
}

This works fine and when a invalid username is checked a UserNotFoundException is thrown by UserBadge.

However, I wonder why this implementation is case insensitive on the given username?

I checked the Symfony code and UserBadge uses the EntityUserProvider class which performs a simple $repository->findOneBy([$this->property => $identifier]) (find by field email in my case). After some search I found related answer, which indicate that wether the find... operate case sensitive or case insensitive most likely depends on the underlying datebase (MariaDB 10 in my case).

Is there any way to control wether the username lookup should work case sensitive or not?

While the case insensitive search is fine in the current project, I would like to make sure, that it stays this way, not matter if I port the project to another machine with another underling database...


Solution

  • You could create a custom User Provider.

    Then in your provider simply call strtolower() on the identifier, and LOWER() on the identifier column.

    A rather simple implementation, assuming you inject the Entity Manager on your User Provider.

    
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        $user = $this->em->createQueryBuilder()
                ->select('u')
                ->from(YourUser::class, 'u')
                ->where('LOWER(u.username) = :username')
                ->setParameter('username', strtolower($identifier))
                ->setMaxResults(1)
                ->getQuery()
                ->getOneOrNullResult();
    
        if (!$user instanceof YourUser::class) {
            throw new UserNotFoundException('No user found for ' . $username);
        }
    
        return $user;
    
    }
    

    Adjust to your application constraints. You can probably move the query itself to the specific entity repository, and create the query there, instead of the provider.