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
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;
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
}
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));
}
I used the code from @Jordon and @Potor Polak to wrap the logic in a standalone service that used the current access token to validate the password. Maybe some needs this:
services.yml
:
app.validator.manual_password:
class: AppBundle\Service\ManualPasswordValidator
arguments:
- '@security.token_storage'
- '@security.encoder_factory'
ManualPasswordValidator.php
:
<?php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
/**
* Class ManualPasswordValidator
*
* @package AppBundle\Service
*/
class ManualPasswordValidator
{
/**
* @var EncoderFactory
*/
protected $encoderFactory;
/**
* @var TokenStorage
*/
protected $tokenStorage;
/**
* ManualPasswordValidator constructor.
*
* @param EncoderFactory $encoderFactory
* @param TokenStorage $tokenStorage
*/
public function __construct(TokenStorage $tokenStorage, EncoderFactory $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
$this->tokenStorage = $tokenStorage;
}
/**
* @param $password
* @return bool
*/
public function passwordIsValidForCurrentUser($password)
{
$token = $this->tokenStorage->getToken();
if ($token) {
$user = $token->getUser();
if ($user) {
$encoder = $this->encoderFactory->getEncoder($user);
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
return true;
}
}
}
return false;
}
}
After this you can inject the ManualPasswordValidator
wherever you want and use it like:
$password = $request->get('password');
$passwordIsValid = $this->manualPasswordValidator->passwordIsValidForCurrentUser($password);
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'));