1

I have a problem regarding my Symfony4 App.

What is happening?

I want to build a "Change Password" function for the currently logged in user. So far everything is fine imo. When I submit the form everything seems to have worked fine (redirect to correct page, page is displayed, .. ). But when I want to navigate to another page I get redirected to my login page due to no authentication. There I found out, that the password was not changed either.

I am very thankful for any kind of help!

EDIT

The log out is happening any time the form is submitted, regardles of errors or not.

Controller

/**
 * @Route("/user/change-password", name="_user_change_password", methods={"GET","POST"})
 * @Template("admin/change_password.html.twig")
 */
public function changePasswordAction(Request $request, UserPasswordEncoderInterface $encoder)
{
    /**
     * @var $user User
     */
    $user = $this->getUser();
    $form = $this->createForm(ChangeOwnPasswordFormType::class, $user);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        $oldPassword = $form->get("oldPassword")->getData();
        $checkPass = $encoder->isPasswordValid($user, $oldPassword);
        if(!$checkPass) {
            $this->addFlash("error", "user.wrong_old_password");
            return array(
                "form" => $form->createView()
            );
        }
        $entityManager = $this->getDoctrine()->getManager();

        $newPassword = $form->get("password")->getData();
        $user->setPassword($encoder->encodePassword($user, $newPassword));
        $user->setUpdatedAt(new \DateTime());

        $entityManager->flush();
        $this->addFlash("success", "user.password_changed");
        return $this->redirectToRoute("_user_change_password");
    }
    return array(
        "form" => $form->createView()
    );
}

Form Type

class ChangeOwnPasswordFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('oldPassword', PasswordType::class, array(
                'label' => 'user.old_password',
                'mapped' => false,
                'attr' => array(
                    'autocomplete' => 'current-password',
                ),
            ))
            ->add('password', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options' => array(
                    'constraints' => array(
                        new NotBlank([
                            'message' => 'password_reset.password.blank',
                        ]),
                        new Length([
                            'min' => 6,
                            'minMessage' => 'password_reset.password.short',
                            'max' => 4096,
                            'maxMessage' => 'password_reset.password.short',
                        ]),
                    ),
                    'label' => 'user.password'
                ),
                'second_options' => array('label' => 'user.password_confirmation'),
                'invalid_message' => 'user.password_mismatch',
                'options' => array(
                    'attr' => array(
                        'autocomplete' => 'new-password',
                    ),
                )
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => User::class,
            'validation_groups' => array("Create")
        ));
    }
}

Twig

{% extends "base.html.twig" %}

{% block body %}
    <h1>{{ ("user.change_password.title")|trans }}</h1>
    {{ form_start(form) }}
    {{ form_widget(form) }}

    <button type="submit" class="btn btn-success">
        {{ ("button.save")|trans }}
    </button>

    {{ form_end(form) }}
{% endblock %}
fehmelchen
  • 203
  • 3
  • 15
  • 2
    your 'password' form field is mapped, so it will update the password on the user. calling to validate the "old" password (happening afterwards) will always fail. since the plaintext password already stored in the user object isn't encrypted. in my humble opinion go more manual and just let the form's `data_class` be null (i.e. an array) and don't provide the user object to it. keep it separate. don't change anythign on the actual object unless its legitimate. – Jakumi Jan 20 '21 at 21:51
  • @Jakumi Works! Thank you very much! Do you know why the session gets invalidated after handling the form? I mean, if this has a security reason, why doesn't get the user redirected to the logout page (or similar). – fehmelchen Jan 21 '21 at 07:19
  • Symfony stores a "digest" of the user in the server session, I assume at the end of the request, and will compare it to the digest of that same user from database at the beginning of the next request. If something changed, the currently logged in user session will get invalidated (could be malicious, could be any reason, it's not really checked, that the user was logged in half a second ago and didn't actively log out). soo, redirection isn't commonly done (there's no necessity really) but rather a login form is shown, if the current page requires privileges. – Jakumi Jan 21 '21 at 07:43
  • also, encoder->isPasswordValid takes the old hash and the old password https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Security/Core/Encoder/PasswordEncoderInterface.php . the problem that appears to happen here is probably not related, but I actually don't know the exact reason why there seems to be a "success" display of the correct page ... – Jakumi Jan 21 '21 at 07:49
  • Yeah, maybe the symfony guys are thinking all devs know what to do (and sure they should ;) ). Thanks anyway! – fehmelchen Jan 23 '21 at 12:33

1 Answers1

0

Just to post a workaround if anyone having the same problem. Just like Jakumi mentioned, I was mapping the password of the user object. Somehow, while validating this by symfony the User gets logged out.

What worked was removing the user object from the form, so here are a few updated snippets:

Controller creating form

$form = $this->createForm(ChangeOwnPasswordFormType::class);
$form->handleRequest($request);

Form Type configureOptions

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array());
}
fehmelchen
  • 203
  • 3
  • 15