I suggest to add something like "form token" to prevent multiple form submissions. This is very troublesome. Unfortunately people often double clicking the submit button. Then the submitted data is repeatedly processed by controller class.
When the form is rendering, you can add a hidden field with a token. The token value will be saved in cookie after form submitting and compare next time during the submission.
I try to fix the problem by using JS but the solution is dependent on the website browsers. Additionally, JS you can easily disable.
Please, think about prevent multiple form submission.
$('button').click(function() {
$(this).attr('disabled', true);
});
Sensitive operations, that change state of something important should be protected with validation rules that also prevent double-submitting.
I think we should not implement it in core.
I can't be responsible for browser setting and used plugins. JS solution is good but in my opinion it doesn't provide 100% efficiency. Your example is the first shield but server solution should works too.
I always thought that it shouldn't be used only JS solutions to improving safety. Also safeguards should be implemented on the server side.
Please, look at this.
I have a model with a static method. This function return hidden field with generated form token. During a function call the value of token is saved in session. The field is joined to every form by calling static method in view file.
Model
public function getHiddenFormTokenField() {
$token = \Yii::$app->getSecurity()->generateRandomString();
$token = str_replace('+', '.', base64_encode($token));
\Yii::$app->session->set(\Yii::$app->params['form_token_param'], $token);;
return Html::hiddenInput(\Yii::$app->params['form_token_param'], $token);
}
When the form submit, function named 'beforeAction' in controller class compare sent token with value in session. The session is cleaned after every action executed. If the values are different, throws the Exception.
Controller
public function beforeAction($action) {
$formTokenName = \Yii::$app->params['form_token_param'];
if ($formTokenValue = \Yii::$app->request->post($formTokenName)) {
$sessionTokenValue = \Yii::$app->session->get($formTokenName);
if ($formTokenValue != $sessionTokenValue ) {
throw new \yii\web\HttpException(400, 'The form token could not be verified.');
}
\Yii::$app->session->remove($formTokenName);
}
return parent::beforeAction($action);
}
In my opinion it is a safer option than JS. When the user repeatedly clicking on the button, the appropriate action will be handled only once.
It would be great if the form token was attached to the form automatically, eg. as a token CSFR (BaseHtml::beginForm()).
Thanks and the happy New Year :)
Wow, what is wrong with simply using the submit event, as in:
$('form').on('submit', function(){
if ($(this).data('requestRunning')) {
return false;
}
$(this).data('requestRunning', true);
[...]
$.post(url, {}, function(json){
$(this).data('requestRunning', false);
}, 'json');
[...]
return false;
});
I know in Yii forms, this is a bit distinct, but the same should hold true (i am doing the same in yii1 and will in 2).
i believe this also should not be in the core, this is something the developer should take care of, or, in last scenario, add an option to enable/disable it, but under the logic explained above, using the data attributes, no tokens or the like as those don't make sense.
I agree with @elinepatryk. I have never understood why this functionality is ignored. I have always used the JS events, but every now and then I am still getting double records from users. It does not feel robust and practice has proven this. Perhabs the implementation by me as the developer is not good enough, but I am eager to get 100% perfection.
If JS cannot be trusted at all times (if that is the matter), because of ie.
then I would definately think its logical to provide AR protection in core.
@dynasource thank you for the grounded explanation of your point. We can not blindly rely JS.
I'm not standing against AR implementation of protection, but currently I don't have a good design idea for this problem. Also there are some disputable topics:
Do you know, how to behave in this cases? (I don't)
thanks. You're welcome!
I must say I didn't think of the scenario in which duplicate records are in fact allowed. But from another perspective, in 99% of the time, duplicate inserts are faulty behavior. Behavior which we are currently accepting as default.
If we put it around. Lets make sure that 99% of use cases are done correctly, and if you want to insert duplicate records, you have to bypass this protection manually.
With respect to an implementation. This could be done by an approach comparable to CSRF, in which a UID is sent with the models in the form. The UID is temporarily remembered (somehow) after a succesful save. Because you connect the UID to the model and not the form, tabular input could be handled.
Another easier option would be creating a unique identifier for saved models. You provide the attributes to prevent double inserts in AR, and the system verifies this on insert. This could be a validator that is connected to your caching component or your session.
99% of the time, duplicate inserts are faulty behavior
you have to bypass this protection manually.
I agree on this.
What about multiple tabs? Is not a case of duplication. It's just one of use cases, when user knows, that he needs to create N records and prepares a bunch of tabs.
hmmm,..this tabs 'user scenario' is new for me. Do you have an example?
Yes. I want to create 3 issues on GitHub. I click New issue
three times with the Ctrl
key pressed and get 3 tabs with issue create forms. I fill these forms and get 3 issues
but the forminput is different? If thats the case, your validator can detect.
The the validation based on key does not cover this case, you need to check DB for the same records
ie.
I know the topic is quite old, but it just came to my attention after dynasource mentioned this ticket in one of my issues.
I never experienced such problems myself, but I think the solution should target the core problem, i.e. the prevention of multiple form submissions. To me, there is a distinction in
Another way to think of scenario _a_, is to think about malicious behavior in general, e.g. a bot which is submitting lots of POST requests to the server (e.g. a spambot). To me, the issue described here does not fit into that scenario. Furthermore, Yii already provides some mechanisms that allow to prevent these scenarios (namely captcha and rate limiter)
Regarding scenario _b_, I think that yii should provide a client side solution for this, similar to what SilverFire suggested in the 2nd comment, e.g. through a boolean property ActiveForm::disableOnSubmit
.
As for a workaround for the others: Maybe its feasable to add a "hidden" captcha to the form, i.e. use a hidden field, prefilled with the captcha solution. Also some modifications to the yii.captcha.js script would be required to update the hidden field after submitting the form but server side validation fails.
I agree with you to make that distinction and to provide solutions on both client & serverside.
With respect to the solution https://github.com/yiisoft/yii2/issues/10498#issuecomment-167775881, this one requires a more advanced approach, as this event will be called twice (one call because of yii.js) and the actual disabling of the submit button stops the form from submitting.
Today I finally developed a solid solution for this issue. I will put it in production and do a PR if its proven stable.
Thread is closed but here are my two cents..
$('form').on('beforeSubmit', function()
{
var $form = $(this);
//console.log('before submit');
var $submit = $form.find(':submit');
$submit.html('<span class="fa fa-spin fa-spinner"></span> Processing...');
$submit.prop('disabled', true);
});
How about long-time-reaction? User Superman click "submit" 2 times (may be with ctrl) at 0.125sec (js check is off)
Very slow server can process request at 2sec before check first request at duplicated but only 1sec before check second click...
Out of framework scope. Closing.
Most helpful comment
I agree with @elinepatryk. I have never understood why this functionality is ignored. I have always used the JS events, but every now and then I am still getting double records from users. It does not feel robust and practice has proven this. Perhabs the implementation by me as the developer is not good enough, but I am eager to get 100% perfection.
If JS cannot be trusted at all times (if that is the matter), because of ie.
then I would definately think its logical to provide AR protection in core.