passwordEncoder generated hash for non-authentication purpose fails on verification - symfony

For a user to register with my system, I'm sending a confirmation mail with a generated link. On click the system should set an active property to true.
To create the confirm link, I generate a md5 hashed random_byte value. This value is part of the link I send to the user. The value is hashed with the passwordEncoder and stored in my database.
When the user clicks the link, the system checks the value against the hash in my database and should return true, to fully activate the user. For some reason the verification fails.
I checked, that the generated $identifier value in register() is the one that gets send to the user. They match. So no mistake there.
At first I wanted to use password_verify() only, but that didn't work out either.
I simply have no clue, why it's not working. My last guess is, that there is some Symfony dark magic going on, that I'm not aware of.
public function register(EntityManagerInterface $entityManager, Request $request, UserPasswordEncoderInterface $passwordEncoder, MailerInterface $mailer)
{
// Register-Form
$form = $this->createForm(RegisterFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var User $user */
$user = $form->getData();
$user->setPassword($passwordEncoder->encodePassword(
$user,
$form['plainPassword']->getData()
));
// create activation hash
$identifier = md5(random_bytes(16));
$user->setActiveHash(
$passwordEncoder->encodePassword(
$user,
$identifier
)
);
// send activation mail with activation hash
$message = (new TemplatedEmail())
->subject('...')
->from('...')
->to($user->getEmail())
->textTemplate('emails/registration.txt.twig')
->context([
'mail' => $user->getEmail(),
'identifier' => $identifier,
])
;
$mailer->send($message);
// persist & flush
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', '...');
//return $this->redirectToRoute('login');
return $this->render('home/index.html.twig');
}
return $this->render('security/register.html.twig', [
'registerForm' => $form->createView()
]);
}
/**
* #Route("/confirm/{email}/{identifier}", name="register.confirm")
*/
public function confirm($email, $identifier, EntityManagerInterface $entityManager, UserPasswordEncoderInterface $passwordEncoder)
{
$user = $entityManager->getRepository(User::class)->findOneBy([
'email' => $email,
'active' => false
]);
if (!$user) {
// fail no user with email
die('no such user');
}
if( !$passwordEncoder->isPasswordValid($user, $identifier) ){
// Hash verification failed
die('hash failed');
}
$user->setActive(true);
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', '...');
return $this->redirectToRoute('login');
}
EDIT:
I tried manually hashing a string with password_hash('a_string', PASSWORD_ARGON2ID) and it throws an exception that PASSWORD_ARGON2ID is an undefined constant. PASSWORD_ARGON2I works. And Symfony somehow uses Argon2ID as my password strings and the activeHash strings generated through encodePassword() start with "$argon2id..." How is that possible?

Ok. So I was running PHP 7.2 via MAMP locally. As (before it was deleted) mentioned in the comments by msg, Argon2ID hashing was probably provided by Sodium. 7.2 password_verify() on the other hand was not able to verify a argon2id hash.
Upgraded to PHP 7.3.7 and no problem anymore.

Related

Unable to guess how to get a Doctrine instance from the request information for parameter "class"

I'm trying to send an email with a token in order to resend an automated password. But in my view i'm retrieving the following error:
/**
* #Route("/forget-email/", name="forget", methods="GET|POST")
*/
public function emailrestore(Request $request, User $user, \Swift_Mailer $mailer)
{
$url = "test";
$form = $this->createForm(ForgetPasswordType::class, $user);
$form->handleRequest($request);
$email = $form['email']->getData();
$user = $this->getDoctrine()
->getRepository(User::class)
->find($email);
if ( $email === $user ) {
$mail = (new \Swift_Message('Hello Email'))
->setFrom('email#email.email')
->setTo($email)
->setBody(
$this->renderView(
// templates/emails/registration.html.twig
'emails/registration.html.twig',array('url' => $url,)
),
'text/html'
);
$mailer->send($mail);
} else{
var_dump("$email");
}
return $this->render('forget/email.html.twig', [
'form' => $form->createView(),
'error' => null,
]);
}
Inside my entity i have email as unique
* #UniqueEntity(fields="email", message="Email already taken")
I'm retrieving the following error:
"Unable to guess how to get a Doctrine instance from the request information for parameter "user"." Why?
Thanks for your explanation for advance
You have "User $user" in your action parameter but you don't have a parameter 'user' in your request, my guess is your ParamConverter can't convert this one into a User object because of that.
make your route a GET route formatted like:
/**
* #Route("/forget-email/{user}", name="forget", methods="GET")
*/
Where user is whatever you want (mail, username, uid, etc ...) has long as it allows you tyo retrieve your user object and have your paramconverter converting it into a Doctrine object ...
You need to specify an initial value for your $user variable
Try this :
public function emailrestore(Request $request, User $user, \Swift_Mailer $mailer)

Symfony - Edit User Entity without changing password

I'm running Symfony 3.4.14 and I developed my own User Bundle, I have a very bad experience with FOS then I don't want to use anymore. My goal is to let the admin users create/edit/remove users, I mean other users account.
I made :
the User entity
the Login form
the Registration form
.. and I'm stuck with the Update form. I want to let the admins edit a user without editing the password, but to give them the opportunity to do it if needed. Below is my EditUserAction in controller :
<?php
/**
* #Route("/admin/users/edit/{id}", requirements={"id" = "\d+"}, name="admin_users_edit")
* #Template("#Core/admin/users_edit.html.twig")
* #Security("has_role('ROLE_ADMIN')")
*/
public function EditUserAction($id, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$user = $this->getDoctrine()->getRepository('CoreBundle:User')->findOneBy([ 'id'=>$id, 'deleted' => 0 ]);
if ( $user )
{
$old_password = $user->getPassword();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// If admin changed the user password
if ( $user->getPlainPassword() )
{
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
}
// If admin didn't change the user password, we persist the old one
else
{
$user->setPassword($old_password);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
}
return array('form' => $form->createView());
}
return $this->redirectToRoute('admin_users');
}
Case 1 (when admin choose to change the user password) works well, but the other case (when admin don't want to change the password) fails. They are no way to let the 2 password inputs empty. I can't get rid of this validation error in the debug toolbar :
Path: data.plainPassword
Message: This value should not be blank
In order to avoid this error, as you can see in my controller above, I try to keep the old one (may not be a best practice, I know).
In your form / entity (where you defined validations) you should write either your own constraint (documentation) or validation callback (documentation). In there you can check - If value is null, don't validate, if not null run your validations.
You need to have two separate methods for both.
public function changePasswordAction(Request $request)
{
// your code for changing password.
}
/**
* #Route("/admin/users/edit/{id}", requirements={"id" = "\d+"}, name="admin_users_edit")
* #Template("#Core/admin/users_edit.html.twig")
* #Security("has_role('ROLE_ADMIN')")
*/
public function editUserAction(User $user = null, Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
//you can directly give your User Entity reference in parameters and you dont need to write an extra query to find user.
if ( $user === null){
//return user not found
}
else if($user->isDeleted() === false)
{
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
//your code
}
// your code
}
return $this->redirectToRoute('admin_users');
}

symfony 3.1 registration email no FOSUser

Nice to meet you.
I'm developping with Symfony 3.1.3 and I'm using the security system offered by the framework, no FOSUser neither Guard.
I have in my controller the typical function login:
public function loginAction(Request $request)
{
// This works
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/loginForm_old.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
And I want to check if the user has activated his account. In the entity User I have the isActive attribute set to false by default and only with the link into the registration email is setted to true.
I have been searching for this issue without results and I'm sure this is something very common, everybody wants to check if the user's email is a good one.
Thanks.
Lets assume that you have an RegistrationController.php class where you store all code that manage about user's registration.
Create a function which sends email to user after registration:
public function sendConfirmationEmailMessage(User $user)
{
$confirmationToken = $user->getConfirmationToken();
$username = $user->getUsername();
$subject = 'Account activation';
$email = $user->getEmail();
$renderedTemplate = $this->templating->render('AppBundle:Emails:registration.html.twig', array(
'username' => $username,
'confirmationToken' => $confirmationToken
));
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setFrom(MAILER_FROM)
->setReplyTo(MAILER_FROM)
->setTo($email)
->setBody($renderedTemplate, "text/html");
$this->mailer->send($message);
}
Create a route associated with function which takes an generated token as argument, then search user by that token and activate if user exist:
/**
* #Route("user/activate/{token}")
*/
public function confirmAction(Request $request, $token)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:User');
$user = $repository->findUserByConfirmationToken($token);
if (!$user)
{
throw $this->createNotFoundException('We couldn\'t find an account for that confirmation token');
}
$user->setConfirmationToken(null);
$user->setEnabled(true);
$em->persist($user);
$em->flush();
return $this->redirectToRoute('user_registration_confirmed');
}
Then when you have a function which actually registers the user you call the sendConfirmationEmailMessage as shown below:
public function registerAction(Request $request)
{
/* All the logic goes here: form validation, creating new user */
/* $user is created user */
sendConfirmationEmailMessage($user);
}
Anyway if isActive() function return false Symfony security system will prevent you from login. Your User entity should implement UserInterface.

How can i get the user with id session in param request with symfony2

I'm trying to do a little API with Symfony2.
I send a session id to my controller with a URL like this:
localhost/symfony2/web/app_dev.php/users/getuser/c5auv7mrp45rnd046cfv0vgl96
Then, in Symfony,
/**
* #Route("/getuser/{sessionId}")
*/
public function getSessionAction(Request $request, $sessionId)
{
// Here is what i'm trying to do
$packJson = array(
'user_id' => $userid
);
$response = new JsonResponse();
$response->setData($packJson);
return $response;
}
So, i would like to retrieve my user Id only with the sessionId argument.
Of course, it will be load from Db
I don't understand the logic between Session object and User Objet
Thanks
I think you want to use a token to identify a user. That means you have one token for each user in your database. If that is correct then it has nothing to do with sessions or a session-object.
you could simple retrieve your user with:
/**
* #Route("/getuser/{token}")
*/
public function getSessionAction($token)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AdminBundle:User')->findOneBy(array('token' => $token);
$response = new JsonResponse();
if (!$entity) {
$response->setData('error' => 'bad token');
return $response;
}
$packJson = array(
'user_id' => $entity->getId()
);
$response->setData($packJson);
return $response;
}

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?
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
}
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;
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'));
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));
}
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.

Resources