5

After looking around I could not find a solution to this. I am getting error below ONLY when submitting thru Ajax. Meaning that I have beforehand submitted the form in the regular Symfony fashion without an issue.

The CSRF token is invalid. Please try to resubmit the form.

What works:

  • the regular form submission, ie, without Ajax
  • with Ajax I checked that $_POST is correctly populated, including the CSFR token, just before the submission on the controller side, as follows:

$form->submit($request->request->get($form->getName('user')));

As requested, see below output of

var_dump($request->request->get($form->getName('user')));

array(8) { 
["name"]=> string(9) "fafdffafa" 
["avatar"]=> string(9) "dfafffafa" 
["cityId"]=> string(1) "6" 
["phone"]=> string(14) "33434343434344" 
["email"]=> array(2) { 
      ["first"]=> string(22) "myemail@gmail.com" 
      ["second"]=> string(22) "myemail@gmail.com" } 
["plainPassword"]=> array(2) { 
      ["first"]=> string(8) "senha444" 
      ["second"]=> string(8) "senha444" } 
["blogSubs"]=> string(1) "1" 
["_token"]=> string(43) "hLhyoRxVYmJ_FWK0FqXmiiEYZMZ77fDAWvxCZMXCtxw" }

Just to confirm that if I just comment out the javascript below the submission will work and the entity will be persisted.

This is the same var_dump, this time for when things work.

array(9) { 
["name"]=> string(12) "dfdfdfdfafaf" 
["avatar"]=> string(13) "dfdfdfdafdafa" 
["cityId"]=> string(1) "8" 
["phone"]=> string(16) "3343434343343343" 
["email"]=> array(2) { 
    ["first"]=> string(22) "myemail@gmail.com" 
    ["second"]=> string(22) "myemail@gmail.com" }
["plainPassword"]=> array(2) { 
    ["first"]=> string(8) "senha444" 
    ["second"]=> string(8) "senha444" } 
["blogSubs"]=> string(1) "1" 
["save"]=> string(0) "" 
["_token"]=> string(43) "hLhyoRxVYmJ_FWK0FqXmiiEYZMZ77fDAWvxCZMXCtxw" }

This is the submit button generated by Symfony, but not captured by js serialization.

<button type="submit" id="user_save" name="user[save]" class="btn-default btn">Créer mon compte</button>

The form ( I am skipping the form $builder as it seems unnecessary )

app/Resources/views/common/register.html.twig

{{ form_start(form, { 'attr': { 'id': 'signup_form' }}) }}
    <div class="contact input-group">
        {{ form_widget(form.name) }}
    </div>
    <div class="contact input-group">
        {{ form_widget(form.avatar) }}
        <span class="input-group-addon" id="info_avatar">
            <i class="fa fa-info"></i>
        </span>       
    </div>
    <div class="contact input-group">
        {{ form_widget(form.cityId) }}
    </div>
    <div class="contact input-group">
        {{ form_widget(form.phone) }}           
    </div>
    <div class="contact input-group">
        {{ form_widget(form.email) }}
    </div>
    <div class="contact input-group">
        {{ form_widget(form.plainPassword) }}
    </div>
    <div class="contact">
        {{ form_widget(form.blogSubs) }}
    </div>
    <div class="contact form-group ">
        {{ form_widget(form.save) }}
    </div>
{{ form_end(form) }} 

The javascript on the same file:

<script>
    $('body').on('submit','#signup_form',function(event) {
        event.preventDefault();
        var str = $("#signup_form").serialize();
        $.ajax({
            url: "/inscription",
            type: "POST",
            dataType:"json",
            data: str,
            success: function (data) {
                            alert(data);
            }   
        });
    });
  </script>

And the controller ( the getErrorMessages() method was found on SO.)

/src/UsedBundle/Controller/RegistrationController.php

namespace UsedBundle\Controller;

use UsedBundle\Form\UserType;
use UsedBundle\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;

class RegistrationController extends Controller
{
/**
 * @Route("/inscription", name="inscription")
 */
public function registerAction(Request $request)
{
    $user = new User();
    $form = $this->createForm(UserType::class, $user);

    if ($request->isMethod('POST')) {
        var_dump($_POST);
        $form->submit($request->request->get($form->getName('user')));
        if(!$form->isValid()){ 
            $errors = $this->getErrorMessages($form);
            var_dump($errors);
        }
        if ($form->isSubmitted() && $form->isValid()) {
            $password = $this->get('security.password_encoder')
                ->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);
            $email = $user->getEmail();
            $user->setUserKey( $email );
            $user->setUserKeyTime();
            $user->setDateReg();
            $em = $this->getDoctrine()->getManager('used');
            $em->persist($user);
            $em->flush();
            return new JsonResponse(array('message' => 'Success!'));
        }
    }else{
        return $this->render(
            'common/register.html.twig',
            array('form' => $form->createView())
        );           
    }
}

protected function getErrorMessages($form) 
{
    $errors = array();
    foreach ($form->getErrors() as $key => $error) {
        $errors[] = $error->getMessage();
    }

    foreach ($form->all() as $child) {
        if (!$child->isValid()) {
            $errors[$child->getName()] = $this->getErrorMessages($child);
        }
    }

    return $errors;
} 
}
BernardA
  • 1,391
  • 19
  • 48
  • Does `$("#signup_form").serialize();` have the CSRF token ? – Oluwafemi Sule Jun 22 '17 at 21:04
  • `var_dump($request->request->get($form->getName('user')));` ? – Max P. Jun 22 '17 at 21:06
  • @OluwafemiSule, yes, as you can see I have a var_dump($_POST) just before submission on the controller to verify that. – BernardA Jun 22 '17 at 21:06
  • @MaxP. fresh from the oven:array(8) { ["name"]=> string(9) "fafdffafa" ["avatar"]=> string(9) "dfafffafa" ["cityId"]=> string(1) "6" ["phone"]=> string(14) "33434343434344" ["email"]=> array(2) { ["first"]=> string(22) "myemail@gmail.com" ["second"]=> string(22) "myemail@gmail.com" } ["plainPassword"]=> array(2) { ["first"]=> string(8) "senha444" ["second"]=> string(8) "senha444" } ["blogSubs"]=> string(1) "1" ["_token"]=> string(43) "hLhyoRxVYmJ_FWK0FqXmiiEYZMZ77fDAWvxCZMXCtxw" } – BernardA Jun 22 '17 at 21:09
  • @Bernard, you may wish to format it and post it in your question as an update, as it looks jumbled-up in the comment – Dennis Jun 22 '17 at 21:31
  • @Dennis, done as requested, thanks. – BernardA Jun 22 '17 at 21:37
  • @MaxP.,@Dennis I believe I found what the issue might be. As said, this works correctly without Ajax. I just tried the var_dump suggested by Max P without Ajax, ie, when things work. The issue seems to be that the javascript serialization does not capture the submit button. I will post on the question how the var_dump looks when it works. That being said, I do not know why js does not get that, and it is clear that Symfony requires it. – BernardA Jun 22 '17 at 21:46
  • mm silly question perhaps but does the submit button have a `name` attribute? – Dennis Jun 22 '17 at 21:54
  • @Dennis. Yes, I just added the code for the button to my question. It's the plain widget generated by Symfony by {{ form_widget(form.save) }} – BernardA Jun 22 '17 at 21:56
  • mmm I know there have been issues in the past as far as using ` – Dennis Jun 22 '17 at 22:18
  • in other words, you may want to use the submit button for event control only, and move any value that you are getting from the submit button into the `` (or other non-button/non-control) form element. – Dennis Jun 22 '17 at 22:20
  • Or fake it to where button's data is submitted to make Symfony happy. – Dennis Jun 22 '17 at 22:25
  • @Dennis, Well, the button is generated by Symfony This is the code on the $builder ->add('save', SubmitType::class, array('label' => 'Créer mon compte')) – BernardA Jun 22 '17 at 22:35
  • @Dennis The documentation is very specific about being a button as well [link] (https://symfony.com/doc/current/reference/forms/types/submit.html). Not sure how I can circumvent that. If you do, please answer it. – BernardA Jun 22 '17 at 22:44
  • @BernardA the Ajax call uses the `/inscription` location and is not aware of environments. If you are using the standard Symfony edition, you may have a problem here with generating the form in `dev` environment and the ajax call going to `prod` environment. Can you check if that is the case? – lordrhodos Jun 23 '17 at 07:50
  • @lordrhodos, you are absolutely right. It does work if I submit the form from /inscription and not from /app_dev.php/inscription. Now, what's the solution? I hope I do not have to code one thing for dev and then have to log somewhere and change all the particulars before moving to prod. – BernardA Jun 23 '17 at 08:16
  • It depends a little bit how you want to generate the routes in front end. I see at least two options here. Disable csrf in dev environment or use [FOSJsRoutingBundle](https://symfony.com/doc/current/bundles/FOSJsRoutingBundle/index.html) to generate routes in frontend. – lordrhodos Jun 23 '17 at 15:55
  • @lordrhodos, I prefer disabling CSRF. It seems cleaner and easier to reset for prod. I just tried and it works, so put it up as an answer and I will accept it, thanks. Also, I do not fancy getting into still another bundle, at least not for now. – BernardA Jun 23 '17 at 16:27
  • it is hacky but my suggestion was along the line of .. add a hidden `` element to compensate for `serialize` not serializing submit control. It looks like Symfony depends on the control to send CSRF, which they probably shouldn't... – Dennis Jun 23 '17 at 19:33
  • @BernardA I will put up the answer once I am back home tomorrow and have access to a decent computer and not just a tablet ;) – lordrhodos Jun 23 '17 at 22:40

1 Answers1

0

As discussed in the comments, the cause of your issue is related to different environments. If you are using the standard Symfony project with the app_dev.php frontend controller your forms will be rendered with a valid csrf token for the dev environment. Your javascript code

$.ajax({
    url: "/inscription",
    type: "POST",
    dataType:"json",
    data: str,
    success: function (data) {
                    alert(data);
    }   
});

is not aware of the Symfony environment so the url /location points to the prod environment, resulting in the error message that the CSRF token is not valid.

To solve this issue you can either make your frontend code aware of the Symfony routing, e.g. by using the FOSJsRoutingBundle. Or you can disable the CSRF protection for the dev environment:

# app/config/config_dev.yml
framework:
    csrf_protection: false
lordrhodos
  • 2,689
  • 1
  • 24
  • 37