1

I have to add a page to my website that will be accessed via a POST request. The request is side-effect-free, hence it is safe for the user to use their browser's "Refresh" button on the page. The reason why it has to be POST and not GET is that the volume of data needed to characterise the request is large (it includes a collection of arbitrarily many GUIDs identifying resources to be operated upon at a later stage in the process).

When the user of a browser refreshes a page that was the result of a POST request, the browser will typically warn them that the form will be resubmitted and may cause an action to be repeated. This is not a concern in this case, because as I said, the action of requesting this page is side-effect-free. I therefore want to communicate to the user's browser that no such warning should be presented to the user if they use the "Refresh" function. How can I do this?

Hammerite
  • 19,804
  • 4
  • 65
  • 82
  • 1
    I don't think it's possible to do with a POST, but maybe you can use GET with some HTTP request headers. – root Feb 02 '20 at 07:04
  • Assuming that all browsers have this "prevent duplicate POST request" popup and that you can't disable that for all browsers (for example using certain attributes on your forms), what if you (temporarily) save the user input on the server? Is that viable? So can you store the input in a database or in-memory, and give the user a unique identifier they (and perhaps only they?) can use to retrieve the results for that input? This implies the Post/Redirect/Get pattern answered below, but with a huge caveat - namely that you're willing to save the input on the server. – CodeCaster Feb 03 '20 at 15:05
  • @CodeCaster, a solution that involves saving the input temporarily on the server could be acceptable. That is to say, such a requirement would not disqualify a candidate solution. All other things being equal, a solution that does not require doing this would probably be superior. – Hammerite Feb 03 '20 at 15:10
  • So can you give the answerers a little more context? Is this a search form? Are the arbitrarily many GUIDs personal, or are they public? – CodeCaster Feb 03 '20 at 15:19
  • @CodeCaster You said using PRG has "a huge caveat - namely that you're willing to save the input on the server". I totally understand in times of General Data Protection Regulation and privacy concerns that web developers have to regard such aspects. But if a user fills out a form and presses "Submit", he / she transmits those data anyhow and wishes something to happen with them. So what exactly is the difference between storing the POST data *temporarily* (for PRG handling) or *permanently* (the actual purpose of the web app)? – Spark Fountain Feb 03 '20 at 15:30
  • @Spark it all depends on the kind of data, the expectations of users and so on. If a search form (which I assume this is) can yield a request so large that it must be turned into a POST, which in turn will respond with a redirect to a GET - with a unique identifier for this particular search query - how long will that URL be around? And for whom will it have to be accessible? – CodeCaster Feb 03 '20 at 15:32
  • Let us say that if there were only one GUID, it would have been perfectly okay to put it in the query string of a GET request, and thus the GUIDs are not particularly secret. The GUIDs do come from a search operation, of sorts; the results of a search are presented to the user and they may then choose to take action on those results, or a subset of them. It is not acceptable to merely resubmit the search parameters a second time, because changes may have been made by others in the meantime that mean that the same search run a second time would yield different results. – Hammerite Feb 03 '20 at 16:14
  • The post could be performed via AJAX. The user can then refresh the data via a refresh button on the page instead of using the browser's refresh button. – johey Feb 04 '20 at 11:20
  • The user must be able to use their browser's refresh button without seeing a warning of any kind. – Hammerite Feb 04 '20 at 11:57

3 Answers3

1

You can solve this problem by implementing the Post/Redirect/Get pattern.

You typically get a browser warning when trying to re-send a POST request for security reasons. Consider a form where you input personal data to register an account or order a product. If you would double-send your data it might happen that you register twice or buy the same thing two times (of course, this is just a theoretical example). Thus, the user should get warned when trying to send the same POST request several times. This behaviour is intended and cannot be disabled but avoided by using the aforementioned PRG pattern.

Diagram of the double POST problem above being solved by PRG.
Image from Wikipedia (published under LGPL).

In simple words, this pattern can be used to avoid double submissions of form data that could possibly cause undesired results. You have to configure your server to redirect the affected incoming POST requests using the status code 303 ("see other"). Then the user will be redirected (using a GET request) to a confirmation page, showing that the request has been successful and will now be processed. If the user now reloads the page, he / she will be redirected to the same page without re-submitting the POST request.

However, this strategy might not always work. In case the server did not receive the first submission yet (because of traffic for instance), if the user now re-submits the second POST request could still be sent.

If you provide more information on your tech stack, I can expand my answer by adding specific code samples.

Spark Fountain
  • 1,716
  • 11
  • 27
  • _"You have to configure your server to redirect all incoming POST requests"_ - no. And if the POST-redirect-GET pattern would solve this (it _can_), that raises new questions: where does the input from the initial POST get saved, and how will this be reusable in a GET request? And what does _"In case the server did not receive the first submission yet (because of traffic for instance)"_ mean? – CodeCaster Feb 03 '20 at 15:02
  • Why do you deny the first statement? Maybe I formulated it too broadly, of course I meant that the affected POST request needs to be redirected *somehow*. Whether this task is performed by a PHP script, an .htaccess configuration or any other kind of server config doesn't matter. – Spark Fountain Feb 03 '20 at 15:09
  • The redirect to let the POST results be retrieved by a GET is only a small part of the solution. The bigger problem there is, see also my comment under the question: where to store the user input in the meantime? – CodeCaster Feb 03 '20 at 15:10
  • @CodeCaster The user input can be easily forwarded by fetching the POST request's body content and serializing it. Depending on the technology used, this could be configured in PHP or a server environment (with regular expressions or whatever). – Spark Fountain Feb 03 '20 at 15:18
  • No, the OP claims the request is too large to be incorporated in a GET. So if you want a Post/Redirect/Get, you'll have to persist the POSTed data somehow (otherwise, why not let the client issue the GET request immediately?). I have no idea where regexes come into play here. – CodeCaster Feb 03 '20 at 15:20
  • Okay, I got your point. Although I wonder whether it makes a noticable difference (regarding performance) between using GET and POST statements - I have no experiences with that comparison. – Spark Fountain Feb 03 '20 at 15:24
1

You cannot prevent the browser from warning the user about resubmitting a POST request.

References
Mozzila forums (Firefox predecessor) discussed the feature extensively starting in 2002. Some discussion of other browsers also occurs. It is clear that the decision was made to enforce the feature and although workarounds were suggested, they were not taken up.

Google Chrome (2008) and other subsequent browsers also included the feature.

The reasons for this related to the difference between GET and POST in rfc2616: Hypertext Transfer Protocol -- HTTP/1.1 (1999).

GET

retrieve whatever information is identified by the Request-URI

POST

request that the origin server accept the entity enclosed in the request as a new subordinate of the resource

This infers that whilst a GET request only retrieves data, a POST request modifies the data in some way. As per the discussion on the Mozilla forum, the decision was that enabling the warning to be disabled created more risks for the user than the inconvenience of keeping it.

Solutions
Instead a solution is to use sessions to store the data in the POST request and redirect the user with a GET request to a URL that looks in the session data to find the original request parameters.

Assuming the server side application has session support and it's enabled.

  1. User submits POST request with data that generates a specific result POST /results
  2. Server stores that data in the session with a known key
  3. Server responds with a 302 Redirect to a chosen URL (Could be the same one)
  4. The client will request the new page with a GET request GET /results
  5. Server identifies the incoming GET request is asking for the results of previous POST request and retrieves the data from the session using the known key.

If the user refreshes the page then steps 4 & 5 are repeated.

To make the solution more robust, the POST data could be assigned to a unique key that is passed as part of the path or query in the 302 redirect GET /results?set=1. This would enable multiple different pages to be viewed and refreshed, for example in different browser tabs. Consideration must be given to ensuring that the unique key is valid and does not allow access to other session data.

Some systems, Kibana, Grafana, pastebin.com and many others go one step further. The POST request values are stored in a persistent data store and a unique short URL is provided to the user. The short URL can be used in GET requests and shared with other users to view the same result of what was originally a POST request.

Steve E.
  • 7,719
  • 5
  • 31
  • 52
  • _"Server identifies the incoming GET request is asking for the results of previous POST request"_ - how, other than storing the data and assigning it said unique key? – CodeCaster Feb 04 '20 at 20:25
  • "You cannot prevent the browser from warning the user about resubmitting a POST request." - Why not? Can you cite a source to back this up? At the moment this is just deemed impossible because you say it is. – Hammerite Feb 05 '20 at 00:14
  • @Hammerite, A good question. It's hard to provide a source as I am not aware of an official requirement either way. It is a feature that browser developers have produced. However consider that [Chrome ignores `autocomplete`](https://stackoverflow.com/questions/12374442/chrome-ignores-autocomplete-off), this demonstrates that even when the browser is instructed to do something, it doesn't always comply. – Steve E. Feb 05 '20 at 01:27
1

You can't prevent all browsers from showing this "Are you sure you want to re-submit this form?" popup when the user refreshes a page that is the result of a POST request. So you will have to turn this POST request into a GET request if you want to prevent this popup when your users hit F5 on that page.

And for a search form, which you kind of admitted this was for, turning a POST into a GET has its own problems.

For starters, are you sure you need POST to begin with? Is the data really too large to fit in the query string? Taking a reasonable limit of 1024 characters, being around 30 GUIDs (give or take some space for repeated &q=), why do you need the search parameters to be GUIDs to begin with? If you can map them or look them up somehow, you could perhaps limit the size of each parameter to a handful of characters instead of 32 for a non-dashed GUID, and with 5 characters per key you could suddenly fit 200 parameters in the query string.

Still not enough? Then you need a POST indeed.

One approach, mentioned in comments, is using AJAX, so your search form doesn't actually submit, but instead it sends the query data in the background through a JavaScript HTTP POST request and updates the page with the results. This has the benefit that refreshing the page doesn't prompt, as there's only a GET as far as the browser is concerned, but there's one drawback: search results don't get a unique URL, so you can't cache, bookmark or share them.

If you don't care about caching or URL bookmarking, then AJAX definitely is the simplest option here and you need to read no further.

For all non-AJAX approaches, you need to persist the query parameters somewhere, enabling a Post/Redirect/Get pattern. This patterns ends up with a page that is the result of a GET request, which users can refresh without said popup. What the other answers are being quite handwavy about, is how to properly do this.

Options are:

Serverside session

When POSTing to the server, you can let the server persist the query parameters in the session (all major serverside frameworks enable you to use sessions), then redirect the user to a generic /search-results page, which on the server side reads the data from the session and presents the user with the results built from querying the database combined with the query parameters from the session.

Drawbacks:

  • Sessions generally time out, and they do so for good reasons. If your user hits F5 after, say, 20 minutes, their session data is gone, and so are their query parameters.
  • Sessions are shared between browser tabs. If your user is searching for thing A on tab 1, and for thing B on tab 2, the parameters of the tab that's submitted latest, will overwrite the earlier tabs when those are refreshed.
  • Sessions are per browser. There's generally no trivial way to share sessions (apart from putting the session ID in the URL, but see the first bullet), so you can't bookmark or share your search results.

Local storage / cookies

You could think "but cookies can contain more data than the query string", but just no. Apart from having a limit as well, they're also shared between tabs and can't be (easily) shared between users and not bookmarked.

Local storage also isn't an option, because while that can contain way more data - it doesn't get sent to the server. It's local storage.

Serverside persistent storage

If your search queries actually are that complex that you need multiple KB of query parameters, then you could probably benefit from persisting the query parameters in a database.

So for each search request, you create a new search_query database record that contains the appropriate parameters for the query-to-execute, and, given search results aren't private, you could even write some code that looks up whether the given parameter combination has been used before and first perform a lookup.

So you get a unique search_id that points to a set of parameters with which you can perform a query. Now you can redirect your user, so they perform a GET request to this page:

/search-results?search_id=Xxx

And there you render the results for the given query. Benefits:

  • You can cache, bookmark and share the URL /search-results?search_id=Xxx
  • You can refresh the page displaying the search results without an annoying popup
  • Each browser tab displays its own search results

Of course this approach also has drawbacks:

  • Unless you use a unguessable key for search_id, users can enumerate earlier searches by other users
  • Each search costs permanent serverside storage, unless you decide to evict earlier searches based on some criteria
Community
  • 1
  • 1
CodeCaster
  • 131,656
  • 19
  • 190
  • 236
  • I wanted to grant an additional bounty in recognition of your detailed answer, but it turns out that due to Stack Overflow's bureaucratic rules I cannot do this in the way I wanted. – Hammerite Feb 07 '20 at 09:30