Bil5
Bil5

Reputation: 502

Symfony2 prevent multiple form submission

I'm working on a a form with Symfony2. I have some entity fields and a csrf token that is correctly rendered thanks to {{ form_rest(myform) }}.

The problem is :

  1. User fills the form and clicks on the submit button (form is then posted)
  2. User quickly press the escape key
  3. User clicks again on the submit button (form is posted again)

Result: an entity (form is binded to an entity) is inserted twice in the database

And that can occur infinitely

I thought that with the CSRF token field, it would prevent this situation but that's not the case. So is there any way to figure it out natively with Symfony framework? If not what possibilities exist?

Thank you in advance!

Upvotes: 4

Views: 7459

Answers (3)

Will B.
Will B.

Reputation: 18416

An alternative approach leveraging Symfony, is to implement submission redirects on the form view controller to the form processor controller.

This works by receiving the form values once and redirecting the user immediately upon the receipt of the values. So if the action is canceled and resubmitted or clicked multiple times, the initial request(s) fail to redirect the user to the form processor. Effectively preventing multiple submissions of data, since a redirect response is returned to the user and the form processor is not invoked.

The redirect using status code 307 acts as shortcut, allowing the entire request to be passed to another controller method and retain the request method type and data. Meanwhile the user won't visually notice a change and won't receive an invalid CSRF token error.

namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class DefaultController extends Controller
{

    /**
     * @Route('/submit', name="submit")
     */
    public function submitAction(Request $request)
    {
       if ($request->isMethod($request::METHOD_POST)) {   
          return $this->redirectToRoute('process', ['request' => $request], 307);
       }
       //... form view
       $form = $this->createForm(FormType::class, $data, ['action' => $this->generateUrl('submit')]);
       $form->handleRequest($request);
       /** 
        * alternative to comparing the request method type above
        * if ($form->isSubmitted()) {
        *    return $this->redirectToRoute('process', ['request' => $request], 307);
        * }
        */
       return $this->render('::form_view', [
          'form' => $form->createView()
       ]);
    }

    /**
     * @Route('/process', name="process")
     */
    public function processAction(Request $request)
    {
       $form = $this->createForm(FormType::class, $data, ['action' => $this->generateUrl('submit')]);
       $form->handleRequest($request);
       if ($form->isSubmitted() && $form->isValid()) {
           //... process form
           $this->addFlash('success', 'Form Processed!');
           return $this->redirectToRoute('submit');
       }

       //... show form errors and allow resubmission
       return $this->render('::form_view', [
          'form' => $form->createView()
       ]);
    }
}

Upvotes: 0

baris1892
baris1892

Reputation: 1051

I'm not sure if this is the right approach, but you can do the following.

In your FormType, set following options:

For Symfony < 4 use intention:

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'csrf_protection' => true,
        'csrf_field_name' => '_token',
        // important part; unique key
        'intention'       => 'form_intention',
    ]);
}

For Symfony >= 4 use csrf_token_id:

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'csrf_protection' => true,
        'csrf_field_name' => '_token',
        // important part; unique key
        'csrf_token_id'   => 'form_intention',
    ]);
}

Then in your controller action you can do something like this using your intention or csrf_token_id:

if ($form->isSubmitted()) {
    // refresh CSRF token (form_intention)
    $this->get("security.csrf.token_manager")->refreshToken("form_intention");
}

This prevents the double submission of the given form.

Upvotes: 11

Marlon
Marlon

Reputation: 186

The CSRF token does not change during a session, so you cannot use it to prevent multiple form submissions.

You could use Javascript to overcome this issue in the font-end, see for example this question Basically you disable the submit button after it is pressed once.

See also http://technoesis.net/prevent-double-form-submission/

Upvotes: 3

Related Questions