15

Say, you are submitting a form, which affects your database (adding records/ deleting them/ updating them) and this is how your request looks like:

POST /application/action=update

Now, say, you are done with your update, so you would like to take the user to the home page.

Response.sendRedirect /application/action=home

This works wonderfully well. User is sent a redirect after POST, so even if the user tries to refresh the page by hitting F5, you are good. However, this will not work if you did this:

requestDispatcher.forward(/application/action=home)

Given that there is a scenario where you have to display different kinds of error / success messages after you are done with your update, you are most likely doing a forward after POST. In such a scenario, how do you avoid update actions from happening twice?

I find it rather amusing that many secure sites (banks) / payment gateways tend to inform the user by placing text on screen, such as "Please don't press back / refresh buttons".

Is there no better way to handling this? Other than requesting the user not to press these buttons? When I last checked, there was something called the 'Vertical Response Cache'. A Filter that would identify uniqueness of your request in a session and tries to send a cached response if the request is duplicate. Are there any simpler ways to solving this classic problem?

Here is a link to the vertical response cache solution I was talking about: http://www.fingo.info/en/articles/_1.html. I am, However, not sure as to how well this really works.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Jay
  • 2,394
  • 11
  • 54
  • 98
  • When I think about it, I notice that stackoverflow, does something similar. When you edit your post and save changes, you are being redirected back to your post. Now, what if you were to show a message "Your edits were successful."? (I know it does sound a bit lame, but this what is popularly known as 'browsing experience for the not visually challenged corporate dumb users'). To show this message, you have to know that your save went through fine, other than sending a really long string in your query string and redirecting, the only way you would do it is FORWARD? – Jay Jul 05 '09 at 04:29

6 Answers6

12

Yes, I believe that you should redirect after a POST, with the exception of API requests. Without doing this not only do you have to worry about getting duplicate POSTs when the user uses the back button, but the browser will also give the user annoying dialogs when they try to use the back button.

Response.sendRedirect works in practice, but tecnically speaking this is sending the wrong HTTP response code for this purpose. sendRedirect sends a 302, but the correct code to use to transform a POST into a GET is 303. (most browsers will treat a 302 just like a 303 if they get it in response to a POST, however)

In general you want the redirect to send the user to whatever view will display the effect of their change. For example, if they edit a widget, they should be redirected to the view of that widget. If they delete a widget, they should be redirected to the view that the widget would have appeared in when it existed (perhaps the widget list).

Sometimes it's nice to have a status message to further drive home the fact that an action occurred. A simple way to do this is to have a common parameter to your views that, when set, will display an action completed message. eg:

/widget?id=12345&msg=Widget+modified.

Here the "msg" parameter contains the message "Widget modified". The one downside to this approach is that it may be possible for malicious sites to give your users confusing/misleading messages. eg:

/account?msg=Foo+Corp.+hates+you.

If you're really worried about this you could include an expiring signature for the message as an additional parameter. If the signature is invalid or has expired, simply don't display the message.

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
  • @Laurence What would you do if had to display multiple messages? Three types of them: Successes, Failures and Warnings? It surely has to be passed on in request scope to your action that renders your Widget View. – Jay Jul 05 '09 at 05:23
  • Sure. That's just an extension to the msg parameter that I described. – Laurence Gonsalves Jul 05 '09 at 06:30
  • @Laurence so you are saying that I would have something like this: msg=Foo+Corp.+whatever&msgType=Success Agreed. How about multiple messages of the different types? Stuff like this is easier to represent in objects or you are looking at a very untidy job of assembling httpparameters – Jay Jul 05 '09 at 13:25
  • These types of messages are usually used to indicate the action performed by the POST. When would you need to display multiple messages at the same time? BTW: if you're concerned about the size of the message you can typically get away with a passing message ID numbers in the URL instead of the actual messages themselves. If you're internationalizing you may already have ID numbers handy. – Laurence Gonsalves Jul 05 '09 at 16:37
2

The best solution to solve the problem of showing status messages to the users after a POST to GET redirect is to use user sessions.

How

Add attributes to user session with value as set of messages to be displayed. for eg.

userSession.put("success_messages", new HashSet<String>(){"Success", "Check your account balance"});
userSession.put("warning_messages", new HashSet<String>(){"Your account balance is low. Recharge immediately"});

And have a filter which scans the user session for these particular attributes and outputs the messages. The filter should delete the attributes after reading once, as the status messages are generally displayed only once.

satish
  • 91
  • 1
  • 6
1

One thought that I've had is to embed a unique ID (probably a random string) as a hidden form field in the form that is being POST-submitted. The ID string can be put in the database as a "transaction ID". Now, when you go to update the database, first check whether there's an existing record with the submitted transaction ID, and if so, assume it's a duplicate and don't change the database.

Of course, as I said, this is just a thought. I don't know what methods are actually used in practice. (I suspect that a lot of less-critical sites just ignore the problem and hope their users will be smart... a losing proposition if I ever saw one ;-)

EDIT: as pointed out in the comments, storing transaction IDs in the database might take up a lot of space, but if that's an issue, you could keep an in-memory cache of all transaction IDs processed in the last 5 minutes/1 hour/1 day/whatever. That should work unless you're up against a determined hacker...

David Z
  • 128,184
  • 27
  • 255
  • 279
  • 1
    I would think that it would be too costly an operation to hit the database every time for transactionIDs. For an application with millions of users, it would be millions of 'just for the sake of it' records - think about clean up and maintenance of this one table. – Jay Jul 05 '09 at 03:48
  • @Jay, nah -- you just need one extra UNIQUE field transactionID on whatever table or view is involved, and REPLACE OR IGNORE -- ta-da!-) – Alex Martelli Jul 05 '09 at 03:53
  • @Alex How about a non-database approach to this problem? What about cookies? – Jay Jul 05 '09 at 03:58
  • Cookies can protect against accidental double-clicks, but if someone wants to circumvent a cookie-based scheme it is, generally speaking, trivially easy to do so. – David Z Jul 05 '09 at 21:09
  • And anyway, there's always at least a bit of risk involved with assuming any particular behavior on the client's part (like honoring cookie headers). – David Z Jul 05 '09 at 21:13
  • "Determined hacker" - vulnerable to a not-so-determined hacker since db IDs tend to be monotonically increasing. – Pat Jan 24 '11 at 19:18
  • @Pat: true, which is why I suggested random strings. – David Z Jan 24 '11 at 19:35
  • A random id is not unique. In fact there is almost certainly guaranteed to be a large number of collisions with the previous results or/and worse other active users. With 250000 active redirects ( note: not active users, just redirects ) and there is going to be a large number of collisions. Random is not a practical nor good solution. – Pat Jan 25 '11 at 06:43
  • With enough bits you can reduce the probability of a collision as low as you want. Not to mention you could also check session IDs, IP addresses, user agents, etc. to drastically diminish the probability of two different requests being mistaken for each other. – David Z Jan 25 '11 at 06:46
  • Well you have essentially reduced the randomness dramatically to the point where the id is "random" per session. At this point, the developer should just make the sessionId a sha-1 or better yet a sha-256 id and make the intra-session result id sequential. This way if there is any collision it can only happen when the session is created and collision detection only happens once per session – Pat Jan 25 '11 at 21:40
  • what if the uniqueID is changed in the hidden field , by a man in the middle of the attack. – Dead Programmer May 05 '11 at 14:00
1

I find it rather amusing that many secure sites (banks) / payment gateways tend to inform the user by placing text on screen, such as "Please don't press back / refresh buttons".

some people find its better to "disable all Back, Refresh event on this critical pages"; I'm not sure if this is good or not.

But your addressed solution "vertical response cache" sounds nice

Ali Abdel-Aziz
  • 249
  • 3
  • 11
  • @Ali disabling back / refresh events? How do you disable your browser's back button / F5?? I have heard of programmers attempting such things and failing miserably. – Jay Jul 05 '09 at 05:30
  • Actually I didn't make it by hand, but I found friends speaking about removing the refresh and back buttons from the browser in the critical pages. And regarding the F5 disabling I found this code snippet but as to disable the F5 key, write this script in the head section: – Ali Abdel-Aziz Jul 05 '09 at 06:46
  • the above code posted at http://p2p.wrox.com/javascript-how/35830-disable-browser-f5-key.html but I guess cases like firefox short cuts should be handled Ctrl + R but as I told you previously I found "vertical response cache" sounds nice what is your problem with this suggested solution. – Ali Abdel-Aziz Jul 05 '09 at 06:48
  • @Ali I see your point. However, relying too much on javascript to do it for you and leaving control on the user side is not a good practice. I was wondering if there is a non-database way of doing it on the server side. – Jay Jul 06 '09 at 07:36
  • The Vertical Reponse Cache has limitations. I am not really sure about this coz I read this a while ago, but it can only do one at a time or just one of these two : handle resubmitting of forms when user presses F5 OR handle resubmitting of form when user presses back and submits again. I can't recall the limitation right now, but it was on these lines... – Jay Jul 06 '09 at 07:38
1

Its a little non-obvious but:

  • create a keyed-object in the user session.
  • the value is a Request + java Future for the result
  • return immediately with a client-side redirect.
  • while the client-side redirect is being handled, have a worker thread work on producing the answer.

So by the time the client browser completes the redirect, getting the new page's images, etc... the results are waiting for the user.

The alternative is to make the user painfully aware of how long the database is taking.

Security Update (2011 Jan 24 ) :

The key is vulnerable to attack since it is part of the response to the client, so

  1. Generate a random key
  2. Use user's session id as a salt to create a SHA-1
  3. Store both the random key and the SHA-1 in the database with (, ) as the primary key. (no separate indexing on the RANDOMKEY.
  4. Use both RANDOMKEY and the SHA-1 as the db lookup.
  5. Do not store the Session Id (avoid privacy issues with being able to corollate many entries to the same user)
  6. Expire the results after 2-3 days. ( Allows a daily batch job to do the clean up and avoids creating problems for user sessions that are semi-long lasting )

This method requires any hacker to know both the session id and the random key.

This approach may seem overkill, but a redirect-hardened mechanism can be used for situations like password resets.

Pat
  • 5,761
  • 5
  • 34
  • 50
0

If you are working with java server side scripting and also using struts 2 then you refer this link which talks about on using token .

http://www.xinotes.org/notes/note/369/

A token should be generated and kept in session for the initial page render, when the request is submitted along with the token for the first time , in struts action run a thread with thread name as the token id and run the logic whatever the client has requested for , when client submit again the same request, check whether the thread is still running(thread.getcurrentthread().interrupted) if still running then send a client redirect 503.

Please look at the ExecuteAndWaitInterceptor of struts 2code, the logic of this combined with token will help out fast click

Dead Programmer
  • 12,427
  • 23
  • 80
  • 112