Reputation: 502
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 :
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
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
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
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