24

What is the best way to implement change password functionality in Symfony2? Right now I'm using this:

$builder->add('password', 'repeated', array(
    'first_name' => 'New password',
    'second_name' => 'Confirm new password',
    'type' => 'password'
));

It should also contain the current password check for security reasons.

Note: I'm not using FOSUserBundle.

Sam Bellerose
  • 1,782
  • 2
  • 18
  • 43
kuboslav
  • 1,430
  • 5
  • 16
  • 31

5 Answers5

51

Since Symfony 2.3 you can easily use UserPassword validation constraint.

Acme\UserBundle\Form\Model\ChangePassword.php

namespace Acme\UserBundle\Form\Model;

use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
use Symfony\Component\Validator\Constraints as Assert;

class ChangePassword
{
    /**
     * @SecurityAssert\UserPassword(
     *     message = "Wrong value for your current password"
     * )
     */
     protected $oldPassword;

    /**
     * @Assert\Length(
     *     min = 6,
     *     minMessage = "Password should be at least 6 chars long"
     * )
     */
     protected $newPassword;
}

Acme\UserBundle\Form\ChangePasswordType.php

namespace Acme\UserBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ChangePasswordType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('oldPassword', 'password');
        $builder->add('newPassword', 'repeated', array(
            'type' => 'password',
            'invalid_message' => 'The password fields must match.',
            'required' => true,
            'first_options'  => array('label' => 'Password'),
            'second_options' => array('label' => 'Repeat Password'),
        ));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\UserBundle\Form\Model\ChangePassword',
        ));
    }

    public function getName()
    {
        return 'change_passwd';
    }
}

Acme\UserBundle\Controller\DemoController.php

namespace Acme\UserBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Acme\UserBundle\Form\ChangePasswordType;
use Acme\UserBundle\Form\Model\ChangePassword;

class DemoController extends Controller
{
    public function changePasswdAction(Request $request)
    {
      $changePasswordModel = new ChangePassword();
      $form = $this->createForm(new ChangePasswordType(), $changePasswordModel);

      $form->handleRequest($request);

      if ($form->isSubmitted() && $form->isValid()) {
          // perform some action,
          // such as encoding with MessageDigestPasswordEncoder and persist
          return $this->redirect($this->generateUrl('change_passwd_success'));
      }

      return $this->render('AcmeUserBundle:Demo:changePasswd.html.twig', array(
          'form' => $form->createView(),
      ));      
    }
}
Lewis
  • 3,375
  • 3
  • 29
  • 26
jkucharovic
  • 4,214
  • 1
  • 31
  • 46
  • 1
    is it possible to change the password without login? – Ajay Patel Feb 13 '14 at 10:49
  • 1
    @AjayPatel no, it's not possible. `UserPasswordValidator` [uses security context](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php#L37) of current authenticated user – jkucharovic Feb 13 '14 at 11:03
  • 1
    Is it possible to get access to the formfields to style it in twig? Because in ChangePassword.php there is only one "newPassword" field. – Zwen2012 Nov 20 '14 at 14:42
  • 1
    @Zwen2012: Yes, as demonstrated here in the guide http://symfony.com/doc/current/reference/forms/types/repeated.html – Igor Carmagna Feb 08 '15 at 07:46
  • 1
    @jkucharovic are getters and setters required for the entity? – Raaz Mar 10 '15 at 08:53
  • 2
    @jkucharovic this works fine with me. but when my password is 'save password' to browsers, for this form 'old password' is pre filled and if password changed more than once, pre filled passwords are wrong password.. So how can i keep always empty 'old password' i tried 'array('always_empty' => false)' but it didn't work. any solution for this? – Narendra Kothule Apr 22 '15 at 20:38
  • 1
    See my answer for short version. This answer is way too long. – Taylan Oct 06 '16 at 10:59
9

You have to either create another model with two fields:

  • one for the current password;
  • and the other for the new one.

Or add a non-persisted property to your user model like the FOSUserBundle does (see the plainPassword property).

So once you checked both current and new password are valid, you encode the new password and replace the old one with it.

Herzult
  • 3,429
  • 22
  • 15
6

Just add this to your form type:

$builder->add('oldPlainPassword', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class, array(
    'constraints' => array(
        new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(),
    ),
    'mapped' => false,
    'required' => true,
    'label' => 'Current Password',
));
Taylan
  • 3,045
  • 3
  • 28
  • 38
4

I use a action from my controller:

public function changepasswordAction(Request $request) {
    $session = $request->getSession();

    if($request->getMethod() == 'POST') {
        $old_pwd = $request->get('old_password');
        $new_pwd = $request->get('new_password');
        $user = $this->getUser();
        $encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
        $old_pwd_encoded = $encoder->encodePassword($old_pwd, $user->getSalt());

        if($user->getPassword() != $old_pwd_encoded) {
            $session->getFlashBag()->set('error_msg', "Wrong old password!");
        } else {
            $new_pwd_encoded = $encoder->encodePassword($new_pwd, $user->getSalt());
            $user->setPassword($new_pwd_encoded);
            $manager = $this->getDoctrine()->getManager();
            $manager->persist($user);

            $manager->flush();
            $session->getFlashBag()->set('success_msg', "Password change successfully!");
        }
        return $this->render('@adminlte/profile/change_password.html.twig');
    }

    return $this->render('@adminlte/profile/change_password.html.twig', array(

    ));
}
Le Hoai Duc
  • 69
  • 1
  • 2
1

Can't You get old password from User before binding form?

// in action:
$oldpassword = $user->getPassword();

if ($request->getMethod() == 'POST') 
        {
            $form->bindRequest($request);

            if ($form->isValid()) 
            {
                // check password here (by hashing new one)
jpass
  • 11
  • 1