5
votes

I discovered the Security Component in CakePHP helps to prevent CSRF by adding tokens as hidden values to forms.

What I was wondering is if there was anyway to prevent duplicate form submissions using this Component or some other component/helper?

In previous projects, I used a unique hash saved in a session, which is read then deleted upon submit. A repeated submit would have that same hash and an error would be produced.

thanks

7

7 Answers

4
votes

I've puted onClick event that disables the button like this:

<?= $this->Form->button('Salvar', [
                    'value' =>'Submit', 
                    'onClick' => 'form.submit();this.disabled=true'
]) 
?>
2
votes

You could implement the same type of thing in Cake as you've done before.

On submit, set a session variable that marks that form as having been submitted. Make sure to put an expiry time after it (within a few seconds should do the trick). If the session variable is there when you process the form (and you're within that expiration time), then you've got a resubmit, so don't save the form data.

I'd recommend doing this within the save(..) method of your model, so you don't need to worry about adding it in multiple code locations.

2
votes

There is a feature in CakePHP 2.x in the security component that allows you to choose to either use the same security token till it expires or just once. Place this in your controllers beforeFilter method:

$this->Security->csrfUseOnce = true;

Find more information here

2
votes

@DoctorFox has already answered it with csrfUseOnce = true, but this will throw you in blackholes that you still have to manage. So the complete solution for me is :

class YourAppController extends AppController {

    public $helpers = array('Html', 'Form');
    public $components = array('Security');

    public function beforeFilter() {
        $this->Security->csrfUseOnce = true;
        $this->Security->blackHoleCallback = 'blackhole';
    } 

    public function blackhole($type) {
        $this->redirect(array('action' => 'index'));
    }

If there is no redirection, you are still open for double form submission.

Ref : CakePHP security component

1
votes

Just do PRG Pattern..It's very simple right?! Well, at least that's what everyone says but no one posts a clear answer! It took me a week of search and digging and then the "Newbie" decided to do something on his own! Here is one way to do it in cakephp (I use 2.0.5):

Regardless of code here is the logic in steps:
1- set data
2- validate (do NOT create() yet)
3- write $this->request->data to a session variable
4- redirect to a saveData action

Inside saveData action:
5- read & save the session's variable
6- DELETE session's variable
7- create()
8- save data to model
9- redirect

Here is an example of how your code might look like.
**Attn: "ourController" and "ourModel"

public function add() {
        if ($this->request->is('post')) {
            if (isset($this->request->data)) {
                $this->ourModel->set($this->request->data);
                if ($this->ourModel->validates()) {
                    $this->Session->write('myData', $this->request->data);
                    $this->redirect(array('controller' => 'ourController', 
                                           'action' => 'saveData',
                                           'ourModel' //optional but recommended
                                          )
                                    );
                } else {
                    $this->Session->setFlash('ourModel could not be saved.');
                     }
          }
.....//the rest of add() function
}

Then you should be redirected (on validation) to this function that redirects you again to index action or wherever your logic takes you!

public function saveData($model) {
        $myData = $this->Session->read('myData');
        $this->Session->delete('myData'); //extremely important
        $this->$model->create();
        if ($this->$model->save($myData)) 
               // or $myData[$model] if you are dealing with multiple models
              {
              $this->Session->setFlash(__($model.' have been saved successfully'));
              $this->redirect(array('controller' => 'ourController',
                                    'action' => 'index'
                                    )
                               );
            } 
        } else{
            $this->Session->setFlash(__($model.' could not be saved'));
        }
        }
    }

A simple self-redirect might work but in most cases you want to redirect to a different view (e.g. to another form or to index view)

I hope this elaboration helps save time on others so not to have to waste a whole week (as in my case) just to do such functionality server-side!

0
votes

Don't know about cake, but try to not display content on a POST request, do a self redirect. The double post will get solved.

0
votes

The Security component should work, furthermore, you can also unset the data just after the post:

unset($this->data['yourModel']);