5

I got a POST form, and it does send the data to the same file, and if use hit the back button in his browser, he can easily just re-send the data, and it'll be still read.

Is there any way to avoid such behaviour?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Cyclone
  • 14,839
  • 23
  • 82
  • 114
  • 1
    It's not an exact duplicate, but the answer is the same: http://stackoverflow.com/questions/4327236/stop-browsers-asking-to-resend-form-data-on-refresh – Levi Morrison Oct 25 '11 at 23:53
  • @LeviMorrison Yea, I've searched a little bit before, and found the same, but I don't really know how to do the trick of the accepted answer there. – Cyclone Oct 26 '11 at 00:05
  • @LeviMorrison Could you tell me why did you downrated my question? – Cyclone Oct 26 '11 at 00:09
  • @Cyclone Probably because this was answered here before (my guess of his opinion, not my opinion). – middus Oct 26 '11 at 00:58
  • possible duplicate of [Prevent form to repost on refresh](http://stackoverflow.com/questions/3013914/prevent-form-to-repost-on-refresh) – James Wilkins Apr 20 '15 at 19:52

6 Answers6

4

There are two general approaches.

POST-REDIRECT-GET

First is the post-redirect-get pattern where the user fills out a form on signup.php which gets POSTed to submit.php which on successful completion sends back a REDIRECT to thanks.php. The browser sees the redirect response and issues a GET for thanks.php. This works because without the redirect, hitting refresh would reload submit.php causing a re-post form data warning and the POST request to be double issued. The redirect solves this problem.

signup.php
-----------
...
<input type="text" name="email">
...

submit.php
----------
...
if ($_POST) {
  // process data
  header('Location: thanks.php');
}
...

thanks.php
----------
...
Thanks
...

NONCE

The second approach is to embed a nonce key in the form. This approach has the added benefit of preventing CSRF attacks as well. The approach here is to inject a hidden guid in your form:

<input type="nonce" value="<?= uniqid(); ?>">

The server will need to keep track of all nonce keys issues (in a database or something) and when it receives a form, it will process the form and delete the nonce key from the database. If the user resubmits the form a second time, the nonce key won't exist and the server can handle that by ignoring or issuing a warning.

Tobias Ribizel
  • 5,331
  • 1
  • 18
  • 33
aleemb
  • 31,265
  • 19
  • 98
  • 114
3

You cannot 100% prevent this if the user wants to do it, however to avoid this happening by accident, look into Post/Redirect/Get.

middus
  • 9,103
  • 1
  • 31
  • 33
  • 1
    To whom it may concern: If you downvote my answer, please tell me why so I can improve. – middus Oct 26 '11 at 00:00
  • Well, it's not the same guy who downvoted the question, because that was me. – Levi Morrison Oct 26 '11 at 00:01
  • @LeviMorrison You shouldnt downrate my question! – Cyclone Oct 26 '11 at 00:05
  • I bet whoever downvoted (which was not me) was because of the strong use of "cannot 100% prevent this" and maybe reading it like you were saying there is no way to do it instead of actually reading the post. – Kai Qing Oct 26 '11 at 00:07
3

The link Levi sent will answer for you. But in case you want an alternative, here is how I do it...

User posts to a class, like yours. same file. In the beginning of the class I do post processing. For this example I will make it very simple...

<?php
session_start();

//set form vars ahead of time so you can pre-populate the value attr on post
$form = array(
    'name' => '',
    'email' => ''
);

if(!empty($_POST))
{
    //do some kind of validation...
    $errors = array();
    if(trim($_POST['name']) == '')
        $errors[] = 'Please enter your name';

    if(empty($errors))
    {
        $_SESSION['message'] = 'Thank you for participating';
        header('location: /form.php'); // same file
        exit;
    }
    else
    {
        // set the form vars to the post vars so you don't lose the user's input
        $form['name'] = $_POST['name'];
        $form['email'] = $_POST['email'];

        $message = '<span style="color:red">';
        foreach($errors AS $error)
        {
            $message .= $error."<br />";
        }
        $message .= '</span>';
        $_SESSION['message'] = $message;
    }
}

if(isset($_SESSION['message']))
{
    echo $_SESSION['message'];
    unset($_SESSION['message']);
}
?>
<form id="some_form" action="" method="post">
    <fieldset>
        <label for="name">Name</label> <input type="text" name="name" value="<?php echo $form['name']; ?>" />
        <br /><br />
        <label for="email">Email</label> <input type="text" name="email" value="<?php echo $form['email']; ?>" />
        <br /><br />
        <input type="submit" name="submit" value="Submit" />
    </fieldset>
</form>

Now you can refresh over and over and not submit the form twice.

Edited to show further example. Obviously your validation and error handling should be a bit more sophisticated than this, but this should get you in the right direction.

Kai Qing
  • 18,793
  • 5
  • 39
  • 57
  • Nice :) Thanks, but is it save?:s As far as I dont know much about security. – Cyclone Oct 26 '11 at 00:06
  • do you mean safe? let's put it this way - the browser window is a single process. It takes one page at a time. The $_POST action will reload the page with post data - which is where our script comes in. As soon as it hits that header line, it terminates that process and loads the new page, which is not passed any post data. You should be perfectly safe. – Kai Qing Oct 26 '11 at 00:11
  • Oh, one more thing: what if I want to check for errors in the $_POST and then display them? I can not since header will destroy whole output. – Cyclone Oct 26 '11 at 00:31
  • you can go the header path only if the validation is clean. If not, load the page as expected except pre-populate the form with the post vars so the user doesn't get pissed and display errors however you choose... I'll modify the answer to reflect this... – Kai Qing Oct 27 '11 at 02:16
1

That works for me. I use smarty, but it can be done without smarty too. It's only an idea. Modify it as you wish.

The HTML form:

<form action="submit_file.php" method="POST">
<input type="hidden" name="submit_edit" value="1">
<input type="text" name="data"/>
</form>

The php in globals.php

if (count($_POST) > 0) {
    if (isset($_POST['submit_edit']) && $_POST['submit_edit'] == '1'
            && time() - $_SESSION['last_request_time'] < 100
            && count($_SESSION['last_request']) > 0
            && serialize($_SESSION['last_request']) == serialize($_POST)) {

        $oSmarty->assign('display_time_alert', '1');
        unset($_POST['submit_edit']);
        unset($_REQUEST['submit_edit']);
    } else {
        $_SESSION['last_request_time'] = time();
        $_SESSION['last_request'] = $_POST;
    }
}

And this in head.tpl

{literal}
    <script>
function showTimeAlert(){
        alert('{/literal}{tr var="Cannot send the same request twice"}{literal}');
    }
    {/literal}
    {if isset($display_time_alert) AND $display_time_alert eq '1'}
    showTimeAlert();
    {/if}
    {literal}



    </script>
{/literal}

Finaly the submit_file.php

require_once("globals.php");

if(isset($_POST['submit_edit']) && $_POST['submit_edit'] == '1')){
//record data
}

$oSmarty->dysplay(some.tpl);//in some.tpl is included head.tpl
1

When you send the form, push a random number into a $_SESSION['stopdupe'] value, and into a hidden field.

When receiving the form for processing:

  1. Check that $_SESSION['stopdupe'] exists, and matches the hidden field's value. (If not, ignore the post)
  2. Unset $_SESSION['stopdupe']
  3. Process the form as normal.

This way, if a user presses the submit button twice, the second request will not be processed.

Another helpful trick is to use the onSubmit javascript event on the form to disable the button, so the user can't easily submit it multiple times.

MrTrick
  • 1,927
  • 12
  • 11
  • Please be aware that this will likely stop having two tabs of the same site open at the same time from working. – middus Oct 26 '11 at 00:02
  • 1
    That is true, @middus. A more complete solution would push a random value into a `$_SESSION['stopdupe']` array and the hidden field, and on post would check for and remove the value from the array. – MrTrick Oct 26 '11 at 00:18
  • That sounds actually pretty clever! No wonder you call yourself MrTrick ;). – middus Oct 26 '11 at 00:27
0

In First Page use one session variable and assign the value to 1. Example:

<form name="frm1" method="post">
    <?php $_SESSION['resend_chk']=1; ?>
    <input type="text" name="a" />
    <input type="submit" name="submit">
</form>

In second page:

if(isset($_REQUEST['submit'])
{
    if($_SESSION['resend_chk']==1)
    {
        insert to db OR and any transaction
        $_SESSION['resend_chk']=0;
    }
}

It will insert once or the transaction will happen once in db and the value of session variable will change, next time when we try to resend the data it will not do any transaction because the value of session variable is already changed.