views:

167

answers:

3

When posting a form to the same PHP page, what is the correct method to find if the page was accidentally refreshed instead of submitted again?

Here's what I'm using right now:

$tmp = implode('',$_POST);
$myHash = md5($tmp);

if(isset($_SESSION["myHash"]) && $_SESSION["myHash"] == $myHash)
{
    header("Location: index.php");  // page refreshed, send user somewhere else
    die();
}
else
{
    $_SESSION["myHash"] = $myHash;
}

// continue processing...

Is there anything wrong with this solution?

UPDATE: An example for this scenario would be inserting a row into a registration table. You would only want this operation executing once.

+1  A: 

I generally prefer having the POST handler do whatever it needs to do and then redirecting the user to a new page with

header("Location: new-page.php");

so they can refresh without messing anything up

Michael Mrozek
Yes, this would be the obvious solution, and I have no problem doing basic CRUD operations this way. What I'm wondering if there is an accepted solution for checking if a form was accidentally submitted by a page refresh.
Dieseltime
But if the user clicks back to the form page and clicks submit again, wouldn't you end up with two of the same posts?
ggfan
@ggfan: yeah, but that sounds like *intentional* behavior now.
Mark
+2  A: 

Let's start with the point that you can't distinguish accidental refreshes from purposeful refreshes. So you can only decide to not allow refreshes at all, or in other words require that each form submit must be unique. The best way to do that is to insert a random token into the form.

<?php
    $token = /* a randomly generated string */;
    $_SESSION['_token'] = $token;
?>
<input type="hidden" name="_token" value="<?php echo $token; ?>" />

After each submit you invalidate the session token. If the token in the session differs from the one submitted with the form, you can discard the POST.

Re comment: You could do this on a per-item basis. For example, here on SO, you may have several question windows open and answer several questions at once. You can make the token on a per-question basis, so the user could have several valid token at any one time, but only one per question.

An example for this scenario would be inserting a row into a registration table. You would only want this operation executing once.

In this case you probably shouldn't be too concerned about the actual POST, but about data consistency as such. You should have some form of unique identification for each user, like his email address. If the address is already registered in the database, you do not register the user again. This is independent of why the user tried to register twice (double submit or actual attempt to register again).

deceze
One of the problems to note with this method is it breaks when multiple windows/tabs are involved - The second tab overwrites the session value of the first, then if the first tab is submitted the token no longer matches.
Brenton Alker
@Brenton Very true, but I'd say any method that discards POSTs by any criterium will have that problem.
deceze
+1  A: 

Using tokens in conjunction with the POST/REDIRECT/GET design pattern seems to be the best available solution.

  • Setting a single-use token would prevent the user from hitting the back button and trying to submit the form again.
  • Redirecting the user and using a view to display the input allows them to hit refresh if they please.
Dieseltime