'Manual authentication check Symfony 2

I'm working on a Symfony 2 application where the user must select a profile during the login process.

Users may have multiples profiles to work with and they only know their own profiles. So first, I need to prompt for username and password, if those are correct, I should not login the user, I need to prompt for profile witch user will use during the session.

So, I show a form with a username and password field, and send it using an Ajax request, that request responds with the profile list if username and password are correct or an error code otherwise. Finally the user logs into the system using username, password and profile.

The problem is that I don't know how to check if authentication data is correct (using all my authentication managers, users providers, etc) to accomplish this intermediate step (prompts for profile) without in fact logging the user.

Can anyone help me with this?



Solution 1:[1]

A problem with @Jordon's code is that it will not work with hashing algorithms that generate different hashes for the same password (such as bcrypt that stories internally its parameters, both the number of iterations and the salt). It is more correct to use isPasswordValid of the Encoder for comparing passwords.

Here is the improved code that works fine with bcrypt:

$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));

$em = $this->get('doctrine')->getManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();

if ($user) {
  // Get the encoder for the users password
  $encoder_service = $this->get('security.encoder_factory');
  $encoder = $encoder_service->getEncoder($user);

  // Note the difference
  if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
    // Get profile list
  } else {
    // Password bad
  }
} else {
  // Username bad
}

Solution 2:[2]

You could do something like this to retrieve the user and manually test the password -

$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));

$em = $this->get('doctrine')->getEntityManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();

if ($user) {
  // Get the encoder for the users password
  $encoder_service = $this->get('security.encoder_factory');
  $encoder = $encoder_service->getEncoder($user);
  $encoded_pass = $encoder->encodePassword($password, $user->getSalt());

  if ($user->getPassword() == $encoded_pass) {
    // Get profile list
  } else {
    // Password bad
  }
} else {
  // Username bad
}

Once you've got your profile back from the client, you can perform the login manually in the AJAX server controller easily enough too -

// Get the security firewall name, login
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, $password, $providerKey, $user->getRoles());
$this->get("security.context")->setToken($token);

// Fire the login event
$event = new InteractiveLoginEvent($this->getRequest(), $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);

Might need a few use lines -

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

Solution 3:[3]

The only way I could authenticate my users on a controller is by making a subrequest and then redirecting. Here is my code, I'm using silex but you can easily adapt it to symfony2:

$subRequest = Request::create($app['url_generator']->generate('login_check'), 'POST', array('_username' => $email, '_password' => $password, $request->cookies->all(), array(), $request->server->all());

$response = $app->handle($subRequest, HttpKernelInterface::MASTER_REQUEST, false);

return $app->redirect($app['url_generator']->generate('curriculos.editar'));

Solution 4:[4]

In Symfony 4, the usage of the UserPasswordEncoderInterface is recommended in Controllers. Simply add a UserPasswordEncoderInterface as a parameter to the function in which you want to check the password and then add the code below.

public function changePasswordAction($old, $new, UserPasswordEncoderInterface $enc) {
   // Fetch logged in user object, can also be done differently.
   $auth_checker = $this->get('security.authorization_checker');
   $token = $this->get('security.token_storage')->getToken();
   $user = $token->getUser();

   // Check for valid password
   $valid = $encoder->isPasswordValid($user, $old);

   // Do something, e.g. change the Password
   if($valid)
      $user->setPassword($encoder->encodePassword($user, $new));
}

Solution 5:[5]

Symfony 5.4

Password validation can be done using UserPasswordHasherInterface

use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

class AuthenticaitonServices
{    
    public function __construct(UserPasswordHasherInterface $hasher)
    {
        $this->hasher = $hasher;
    }

    public function validate($request)
    {
         $form = [
            "username" => $request->request->get("_username"),
            "password" => $request->request->get("_password")
         ];

         if(!$this->hasher->isPasswordValid($user, $form['password']))
         {
             // Incorrect Password
         } else {
             // Correct Password
         }

isPasswordValid returns a bool response

If anyone checking solution for password validation in Symfony 5.4. Above code is for validating password posted from a login form.

Hope this is helpful.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Benoit Duffez
Solution 2 Alsciende
Solution 3 Diego Castro
Solution 4 c42
Solution 5 ArtisanBay