2

I want to prevent F5 resubmit when user send form data via Post method.

  • I dont want the user to be redirected to the same page;
  • I need to flash a message when correctly submited;
  • After submition clean all the $_POST
  • I cant use javascript, only PHP;

So, first, i have a page with a form to submit info/data , when correctly submited i want to flash a message with positive feedback, and clean all data in $_POST['vars'].

PHP CODE:

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $error= '';  

    if (!$_POST['name']) { 
        $error.= '- Introduza o seu Nome.<br>'; 
    }

    if (!$_POST['email']) {
        $error.= '- Introduza o seu Email.<br>';  
    }

    if (!$_POST['message']) { 
        $error.= '- Introduza a sua mensagem.<br>';  
    }

    if (empty($_POST['check'])) { 
        $error.= '- Por favor, confirme que é um humano.<br>'; 
    }

    if ($error) { 
        $result="Temos erros no formulário. Por favor corriga os seguinte(s):<br> $error";
    } else { 
        mail("email@email", "Mensagem de Contato", "Nome: ".$_POST['name']. "Email: ". $_POST['email'] . "Mensagem: " . $_POST['message'] ); 
        $result='A sua mensagem foi enviada. Obrigado<br>';  
    }
}
?> 

HTML:

<div class="container">
  <div class="row">
    <div class="col-md-6 col-md-offset-3">
    <h1>Formulário de Contato</h1>
    <p>Envie mensagem pelo formulário em baixo</p>
    <?php if (isset($_POST['submit'])) {
      echo $result;
    }
    ?>
    <br>
      <form method="post" action="" role="form">
        <div class="form-group">
          <input type="text" name="name" class="form-control" placeholder="Coloque aqui o seu nome" value="<?php
            if (isset($_POST['name'])) {
              echo $_POST['name'];
            }
          ?>">
        </div>
        <div class="form-group">
          <input type="email" name="email" class="form-control" placeholder="O seu email" value="<?php
            if (isset($_POST['email'])) {
              echo $_POST['email'];
            }
          ?>">
        </div>
        <div class="form-group">
          <textarea name="message" name="message" class="form-control" cols="30" rows="10" placeholder="Escreva a sua mensagem"><?php
            if (isset($_POST['message'])) {
              echo $_POST['message'];
            }
          ?></textarea>
        </div>
        <div class="checkbox">
          <label>
            <input type="checkbox" name="check" >Confirma que é humano.
          </label>
        </div>
        <div align="center">
        <input type="submit" name="submit" class="btn btn-secondary" value="Enviar Mensagem">
        </div>
        </div>
      </form>
    </div>
  </div>
</div>
David Jones
  • 4,275
  • 6
  • 27
  • 51
bfaria
  • 153
  • 1
  • 1
  • 8

1 Answers1

4

Let us see the requirements:

  • I cant use javascript, only PHP;
  • You don't want redirection.

So when the browser does receives a FORM, it can only submit it to the PHP. No Javascript/AJAX shenanigans are allowed. So pressing F5 will be unpreventable.

"Clearing $_POST" means nothing - you cannot, from the server, modify the data that might be (re) posted by the browser, unless the browser already trusts you and has downloaded the appropriate Javascript.

Being able to remotely change _POST once the transaction has completed would be a terrifying security breach (think e-payments).

But all you want to do is not to repeat an operation. You do not need to clear $_POST to do this. The only thing we can do then is to recognize a resubmission and treat it differently (in this case, not sending a mail again); and that's all we need to do.

We do this by adding a unique, non-reusable ID in the FORM, generated e.g. by uniqueid() when the page is made in PHP:

<input type="hidden" name="nonce" value="0cf8059606c73ab872cd8e0064" />

The browser receives the form, and the user submits it. The various fields populate the _POST.

The server receives the POST, and saves the nonce in a $_SESSION array... and checks this is the first time it saw that nonce in a _POST.

if (!in_array($_POST['nonce'], $_SESSION['posts'])) {
    // It is the first time. We add it to the list of "seen" nonces.
    $_SESSION['posts'][] = $_POST['nonce'];
    do_something_with($_POST);
} else {
    print '<div class="errormessage">Please do not resubmit.</div>';
}

Or we can simply run the test and die if it is not successful:

if (in_array($_POST['nonce'], $_SESSION['posts'])) {
    die('Não atualize a pagina. Click <a href="/">aqui</a> para voltar para a home.');
}

Further improvements (for other scenarios)

Of course, if the page generated in answer to the submission has the FORM again (for example to be able to insert several records one after the other), you need to regenerate the uniqueid hidden field.

So F5 will submit a uniqueid "already seen" and ignore the _POST fields altogether, while a new, honest submission utilizing the new data received will be supplied with a new, as yet unseen value that will therefore allow posting.

To be even more sure, you can generate the uniqueid and store it in session before the submission (i.e., when you first open the form). Then, on submit, you will only accept the IDs that you have generated for that session:

if (array_key_exists('nonce', $_POST)) {
    $nonce = $_POST['nonce'];
    unset($_POST['nonce']); // for transparency with extant code
    if (array_key_exists($nonce, $_SESSION['securePosts'])) {
        if ($_SESSION['securePosts'][$nonce]) {
            $error = false;
            // Punch the ticket so it can't be reused
            $_SESSION['securePosts'][$nonce] = false;
        } else {
            $error = 'FORM resubmitted';
        }
    } else {
        $error = 'Someone sent a fake FORM or a FORM from an expired session';
    }
}

if (!$error) {
    // We can treat $_POST as legit.

    // Here the code that "does something".
}

$uniqueid = secureUniqueId();
$_SESSION['securePosts'][$uniqueid] = true; // Ready to be triggered

print "<input type=\"hidden\" name=\"nonce\" value=\"{$uniqueid}\" />";
// If no error, empty DIV
print "<div id=\"error\">{$error}</div>";

Post/DontRedirect/Get?

The above approach does not supports bookmarks to the "success" page.

To be able to "bookmark" the "result" page, you would need, for starters, to put the unique ID in the URL:

<form method="POST" action="?nonce={$nonce}">

This way, when you click SUBMIT the same page receives the nonce in the $_GET['nonce'] parameter. Absence of nonce means that we need to generate an empty form.

Then, upon submitting you need to store the nonce in the database or somehow persist the nonce together with the results:

nonce               results (in separate columns)
eb72ab1736a32...    invoice=1234,amount=32,...

At this point, when you receive a nonce,

1. it is not in the database, and there is POST.
   - do whatever needs to be done and update the database accordingly.

2. is in the database, and there is a POST.
   - it is a resubmission. Ignore it, or display an error

3. is not in the database, and there is no POST.
   - it's a prankster, or a database error.

4. is in the database, and there is no POST.
   - is a bookmark. Display the success page as in (1).

If the user presses F5, he is in case (2). If he saves the page as a bookmark, the nonce in the URL comes from the recently done submission, so the user is in case (1), and is set to proceed as per case (4).

LSerni
  • 55,617
  • 10
  • 65
  • 107
  • great workaround, my only issue is that he specifically asked for "After submission clean all the $_POST" – Serg Chernata Jan 11 '17 at 14:43
  • That isn't actually clear to me - I can *ignore* the _POST (or, yes, clear it), but "after submission" the POST data no longer exist. I cannot change the history of the browser client from the PHP server. So what does 'clean _POST' mean? After submission I'm on a *different* page (even if it's the same HTML as the one before). I can just *not* pre-fill the values of the input fields, and the next submit will be fresh. – LSerni Jan 11 '17 at 14:54