tags:

views:

1467

answers:

8

A while back, online apps used to say, "do not click submit more than once." That's gone now, right? How do you guard against that in, say, PHP?

One solution I'm using involves putting a variable in the Session, so you cannot submit to a page more than once every 10 seconds. That way the database work will have completed so the normal checks can take place. Obviously, this feels like a hack and probably is.

Edit: Thanks everybody for the Javascript solution. That's fine, but it is a bit of work. 1) It's an input type=image and 2) The submit has to keep firing until the Spry stuff says it's okay. This edit is just me complaining, basically, since I imagine that after looking at the Spry stuff I'll be able to figure it out.

Edit: Not that anyone will be integrating with the Spry stuff, but here's my final code using Prototype for the document.getElementByid. Comments welcome!

function onSubmitClick() {
    var allValid = true;
    var queue = Spry.Widget.Form.onSubmitWidgetQueue; 
    for (var i=0;i<queue.length; i++) {
        if (!queue[i].validate()) {
            allValid = false;
            break;
        }
    }

    if (allValid) {
        $("theSubmitButton").disabled = true;
        $("form").submit();
    }
}

For some reason, the second form submit was necessary...

+1  A: 

One solution is to immediately disable the button on click using Javascript. This obviously relies on javascript being on on the client browser.

The server side trick is better since it will catch race conditions between multiple browser windows if the user is editing the same record twice.

Soviut
A: 

I usually disable the submit button after it's pressed. Session throttling is good against direct attacks, but I do not want to mix interface logic where it doesn't belong.

Eran Galperin
Well, it's a secondary control, right? I mean, this is not a UI question: if there are two submits for any reason, you want to throttle them. Until you actually control the user's computer...
Yar
It is a UI decision, since allowing that form to be resubmitted is the problem. It's probably also related to the user not getting enough feedback that something is processing - again a UI failure.
Eran Galperin
Not sure what I think yet... I do think you're getting into the user's head and assuming he or she is logical, which is not the case. On the other hand, you have to assume the UI is functioning...
Yar
It's not logical - it's natural. If you don't give the user feedback, he will wonder if he the form was sent or not - causing him to try to submit again. Usually a problem with slow connections and latency.
Eran Galperin
True Eran, you're right about this UI case. If the UI is not designed correctly, a user SHOULD click again, which is not what we want.
Yar
+7  A: 

This is an excellent example of what jQuery is useful for (you can do it in any Javascript though). Add this code:

$("form").submit(function() {
  $(":submit",this).attr("disabled", "disabled");
});

And it disables submit buttons once clicked once.

cletus
Yeah, was going to say: do I really need JQuery just to disable a thing that has an id? Always liked that $( thinger, though...
Yar
you can use plain javascript to do it as well, as cletus mentioned
Eran Galperin
Yes plain JS can do it too but (imho) jQuery is much more elegant. For one thing, the above snippet can be in your standard included Javascript file rather than being littered in your HTML and it'll work on any form.
cletus
Oh plus theres many other good reasons to use jquery. :)
cletus
+1 - A library like jQuery or Prototype will save you heaps of time and will let you add all sorts of interface niceties and "grace notes" to your site in a way that degrades nicely.
Toby Hede
You can actually source JQuery from Google APIs now, so it cuts down on your bandwidth usage, and there's a good chance its been cached on a user's browser if they've been to any Google sites.
Soviut
Thanks everybody! In my own defense, the "yeah I was going to say" in my first comment was a response to the fact that Cletus already said what I was going to say. But yes, I'll have to get into one of the Javascript libraries soon...
Yar
never disable button for serious stuff. if the page load is slow, user will stop the page loading and wouldn't be able to continue as the button is disabled. he will then have to reload the page and enter the form paingfully again! -1
dusoft
Um, thats exactly when you should disable submit buttons, otherwise they'll just click submit again.
cletus
No, that's exactly why you should not disable any buttons and have the server gracefully handle multiple submits.
Michael Borgwardt
@dusoft you're right but if you use a onDomReady events, you are able to turn on the trick and don't bother people who doesn't load the whole page..
Boris Guéry
+2  A: 

As others have noted, you can disable the button. I like server-side checks better, though - JS may be disabled, user might hit refresh (although if you're properly using POST that will generate a warning), etc.

You can add a timestamp to the form and track it in the session - require the POSTed timestamp be greater than the tracked one. That will prevent most double-posts without noticeably affecting the UI.

Peter Stone
+2  A: 

It's also important to note that PHP's default behaviour if it detects the user has "cancelled" the request (by closing the browser, pressing "stop", or perhaps pressing Submit a second time) is to stop executing the script. This is undesirable if you're doing some sort of lengthy transaction. More details here.

Artelius
Does HTTP communicate the cancelling of a request????
Yar
No, but the connection might be dropped.
Jasper Bekkers
My question was if you hit stop on a request, does HTTP communicate that?
Yar
@Daniel: it depends on the web browser.
Milan Babuškov
HTTP runs over TCP, which *can* communicate a connection termination. The browser will *probably* close the TCP connection when you hit Stop.
Artelius
+20  A: 

You should do both client- and server-side protections.

Client side - disable button e.g. by jquery as cletus has described.

Server side - put a token in the form. If there are two submissions with the same token, ignore the latter. Using this approach, you are protected against CSRF.

ya23
Much better than the (current) higher rated answer. I wonder if "ignore the latter" should actually be "don't change any state" since other behaviour, such as a 'success' message, should still occur?
Bobby Jack
Thanks. No, why would you display the success page? You should either display error or reload the page with warning message. The rationale is when the submit button is disabled, user is not able to send the request again. If he manages to do it anyway it's because he has js disabled or tries to hack
ya23
Fascinating, I guessed that CSRF might exist but I didn't know what it was called. I also don't know why I didn't see this answer back in March, but thanks!
Yar
Glad I helped :)
ya23
+2  A: 

I would think the best option is to let the PHP script first set a flag in the session array to indicate that it is processing a form. That way a second request can be set to wait until the original request has completed (use a sleep call server side waiting for the flag to clear).

It is important not to interfere too much with the submit button and process, because the nature of the web is one of uncertainty; the user may want to click a second time if no answer has arrived, and you don't want to lose their data. That could occur if the first request is lost and you have disabled their only way to submit (and therefore store) the data.

One cleaner solution is to submit the request, and use javascript to display a message saying 'Processing', so that the user can see that something is happening, but they are not prevented from re-submitting the data.

Phil H
Thanks for your answer, I agree (but most people do not).
Yar
+1  A: 

I've done a simple version of this with javascript when I was working with ASP.NET AJAX but it should work in any case where your button has an actual ID.

I take the following steps in the button onclick event:

  1. Disable the button that triggered the onclick event
  2. Store the button id in a magic variable closureId that I can reference later via closure
  3. Use the setTimeout function to execute a dynamically defined callback after the specified number of milliseconds (5000ms = 5 seconds)
  4. In the callback function I can reference the magic closureId and re-enable the button after the timeout

Below is a simple HTML button you can throw into a test.html file by itself to play with:

<input id="btnTest" type="button" value="test" onclick="var closureId=this.id; this.disabled = true; setTimeout(function(){document.getElementById(closureId).disabled = false;}, 5000);>

You should still be doing server-side checks as others have suggested but in my own instance the submit also did a server side validation of business logic. The user would submit their form and then have to wait to see if the business logic on the server-side would actually allow that entry or not. It also helped out during development when I could take my server down and still have the form submission fail decently.

Note I call that closureId variable 'magic' because I don't have a firm grasp of how it works-- I just realized that calling this.id didn't work because this is a reference to the timeout function that is executing the dynamic callback function and doesn't have any sort of DOM id.

I couldn't find any other way to get a reference to the original event (this.this.id doesn't work) but lexical scoping is somehow allowing me to still access the closureId variable as it was defined at the time of the original button click.

Feel free to fix/comment if you know of a better way!

nvuono
Thanks for that. I'm not a Javascript expert, but I cannot stand code jammed into onX methods... if you move your definition of the closureId to a method, and then pass the this.id TO the method... well, you'll naturally use variables to solve the problem as you did. But you'll see things clearer, though your code may be the same.
Yar