views:

1212

answers:

6

A common web problem is where a user clicks the submit button of a form multiple times so the server processes the form more than once. This can also happen when a user hits the back button having submitted a form and so it gets processed again.

What is the best way of stopping this from happening in ASP.NET MVC?

Possibilities as I see it are:

  1. Disable the button after submit - this gets round the multiple clicks but not the navigation
  2. Have the receiving action redirect immediately - browsers seem to leave these redirects out of the history
  3. Place a unique token in the session and on the form - if they match process the form - if not clear the form for a fresh submit

Are there more?

Are there some specific implementations of any of these?

I can see the third option being implemented as an ActionFilter with a HtmlHelper extension in a similar manner to the anti-forgery stuff.

Looking forward to hearing from you MVC'ers out there.

+1  A: 

This really isn't MVC specific, but the pattern we follow on our web pages is that actions are performed with AJAX calls, rather than full page POSTs. So navigating to a url never performs an action, just displays the form. The AJAX call won't be in the history

Clyde
A: 

Along with the disabling of buttons, you can add a transparent div over the entire web page so that clicking does nothing. We do this at my work and add a little friendly label saying processing request..

nickyt
+4  A: 

You should always return a redirect as the HTTP response to a POST. This will prevent the POST from occuring again when the user navigates back and forth with the Forward/Back buttons in the browser.

If you are worried about users double-clicking your submit buttons, just have a small script disable them immediately on submit.

mookid8000
what about our lovely non js users?
redsquare
Scripts can be non-intrusive and inserted in ways that will not disturb non-js browsers/user. If you are still worried about dual form submissions, you need some server-side logic to handle that.
mookid8000
thats what I was getting at. The logic is better implemented server side than relying on js
redsquare
+2  A: 

You might want to look at the Post-Redirect-Get (PRG) pattern:

Steve Willcock
PRG pattern doesn't solve the problem of user clicking submit multiple times.
DSO
No, it doesn't - it does solve the back button problem though
Steve Willcock
you can easily prevent the double clicking with a simple javascript function.
Mademoiselle Vagin Cul
+8  A: 

Often people overlook the most conventional way to handle this which is to use nonce keys.

You can use PRG as others have mentioned but the downside with PRG is that it doesn't solve the double-click problem, it requires an extra trip to the server for the redirect, and since the last step is a GET request you don't have direct access to the data that was just posted (though it could be passed as a query param or maintained on the server side).

I like the Javascript solution because it works most of the time.

Nonce keys however, work all the time. The nonce key is a random unique GUID generated by the server (also saved in the database) and embedded in the form. When the user POSTs the form, the nonce key also gets posted. As soon as a POST comes in to the server, the server verifies the nonce key exists in its database. If it does, the server deletes the key from the database and processes the form. Consequently if the user POSTs twice, the second POST won't be processed because the nonce key was deleted after processing the first POST.

The nonce key has an added advantage in that it brings additional security by preventing replay attacks (a man in the middle sniffs your HTTP request and then replays it to the server which treats it as a legitimate).

aleemb
Interesting idea, one thing I would add is that you should put the sequence in a transaction: 1) check nonce 2) process 3) delete nonce. If you don't do this atomically you have a race condition where the post can be processed multple times, esp. if processing takes a while to finish (and the user is impatient).One thing I'm still not clear about is, if the POST detects a duplicate submission, what should be returned to the user? A page indicating that the post is in progress?
DSO
I would delete the nonce key first and then do the processing to keep things simple. How to handle a dupe is a design decision and up to you. However, a descriptive response of what just happened would help the user appreciate your web app even more.
aleemb
Have you seen an implementation of nonce keys in ASP.NET MVC using ActionFilters perhaps? I can imagine this would make the implementation very simple and reusable. It could also use a DB or simply a session variable.
WooWaaBob
Keep in mind that you'd need to store the nonce key in a database if you are running on a web farm. Otherwise the two posts could go to different servers (though sticky requests can help with this).
Richard Szalay
WooWaaBob, You may want to write an action filter to validate the incoming nonce key in your update function within your model and an html helper to inject the nonce key into your view.
aleemb
WooWaaBob, also look up AntiForgeryToken for MVC. Your implementation would be very similar to this. AntiForgeryToken doesn't generate a new key for each form otherwise it could be used directly for this. If you can find some way to have it generate a new token for each page load, that would be quite elegant as you'd prevent CSRF, double posting and replay attacks all in the same shot. (You could pass in a random salt to AntiForgeryToken to have it generate a new token each time but the problem is that it would not be available to the controller since it's statically typed in the attribute).
aleemb
A: 

If I understand it properly, the nonce is generated on form.asp for example. And once POSTed, the form_process.asp page looks for the nonce. If it has it and it matches something in a database, it processes. Otherwise it fails. Is this correct in terms of where the nonce is generated, used, and how the process page understands it?