<

How to prevent a browser refresh from ruining your day

Published on
9,281 Points
3,181 Views
1 Endorsement
Last Modified:
Approved
Consider the following scenario: You are working on a website and make something great - something that lets the server work with information submitted by your users.

This could be anything, from a simple guestbook to a e-Money solution. But what happens when a user refreshes the website, and the information gets re-sent ? At best the user will create 2 identical entries in a guestbook, at worst your client will send his payment twice.

The information can be re-sent due to a multitude of reasons. The most common one is that the website does not load fast enough and the user gets antsy - and inevitably decides hitting F5 is a grand idea. The warning dialogue that may come up about re-sending data is blissfully ignored, and the deed is done.

Consider the following code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Refresh woes</title>
    </head>
    <body>
        <?PHP
        if ($_GET['submit'] == 1) {
            echo 'Sending money - lets hope the client does not refresh and accidentially sends money twice!';
        } else {
            ?>
            <form action="<?= $_SERVER['PHP_SELF'] ?>?submit=1" method="post">
                <input type="submit" value="Send Money" />
            </form>
            <?PHP
        }
        ?>
    </body>
</html>

Open in new window


This code creates a simplified site that consists of a single Submit-button that, when clicked, leads to a different part of the same page that we DON'T want to have executed twice.

My preferred solution for this problem is a session with a transaction token. When stowed away in a function, it can be easily employed and adapted to any situation.

First, I will show the function that we will be using, it has comments for every part of the code, so you should be able to follow what's going on fairly easily:
/* createtoken()
 * Function to create a new token 
 * Returns the new token
 */

function createtoken() {
    /* First we need to check if a session token exists already */
    if (!count($_SESSION['token'])) {
        /* There is no previous session token for this client, so we need to create a new token */
        $txid = rand(1, 99999);
        /* Here we specify the amount of uses this token should have. 1 is recommended */
        $_SESSION['token']['_' . $txid] = 1;
    }

    /* Next we check if the current token is out of uses */
    if (current($_SESSION['token']) == 0) {
        /* We preserve the old token to make sure it does not randomly roll the same number again */
        $oldtx = key($_SESSION['token']);
        unset($_SESSION['token']);

        /* This loop prevents the same transaction ID from re-appearing. */
        do {
            $txid = rand(1, 99999);
        } while ('_' . $txid == $oldtx);

        $_SESSION['token']['_' . $txid] = 1;
    }

    /* And finally, we return the new transaction ID */
    return $curtx = key($_SESSION['token']);
}

Open in new window


After you have written the function, you can very easily apply it to all your forms and pages. You only need to follow these guidelines:
1) Make sure you call session_start(); at the top of each page
2) In forms, pass along the transaction ID ( e.g. <input type="hidden" name="tx" value="<?=$curtx?>"/> )
3) Use $_SESSION['token'][$_POST['tx']]--; on pages which should use up a token.
4) Call $curtx = createtoken(); whenever you want to refresh the token.

If this sounds confusing, take a look at the final code below and I am sure you will understand:
<?PHP
/* We have to start the session in order to use the session functionality */
session_start();

/* createtoken()
 * Function to create a new token 
 * Returns the new token
 */

function createtoken() {
    /* First we need to check if a session token exists already */
    if (!count($_SESSION['token'])) {
        /* There is no previous session token for this client, so we need to create a new token */
        $txid = rand(1, 99999);
        /* Here we specify the amount of uses this token should have. 1 is recommended */
        $_SESSION['token']['_' . $txid] = 1;
    }

    /* Next we check if the current token is out of uses */
    if (current($_SESSION['token']) == 0) {
        /* We preserve the old token to make sure it does not randomly roll the same number again */
        $oldtx = key($_SESSION['token']);
        unset($_SESSION['token']);

        /* This loop prevents the same transaction ID from re-appearing. */
        do {
            $txid = rand(1, 99999);
        } while ('_' . $txid == $oldtx);

        $_SESSION['token']['_' . $txid] = 1;
    }

    /* And finally, we return the new transaction ID */
    return $curtx = key($_SESSION['token']);
}

/* start the token system */
$curtx = createtoken();
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Refresh woes</title>
    </head>

    <body>
        <?PHP
        if ($_GET['submit'] == 1) {
            /* This information needs to be added to the part of the page that you DON'T want to execute twice
             * First we need to test if we currently have a valid token, and if we do not, we output a error. */
            if ((!isset($_SESSION['token'][$_POST['tx']]) || $_SESSION['token'][$_POST['tx']] <= 0)) {
                echo '<p>Prevented page reload from re-sending money.</p>';
            } else {
                /* Removing one token use */
                $_SESSION['token'][$_POST['tx']]--;
                echo '<p>Sending money - now refresh-protected!</p>';
            }
        } else {
            ?>
            <form action="<?= $_SERVER['PHP_SELF'] ?>?submit=1" method="post">
                <!-- We need to pass along the current transaction ID -->
                <input type="hidden" name="tx" value="<?= $curtx ?>"/>
                <input type="submit" value="Send Money" />
            </form>
            <?PHP
        }
        ?>
    </body>
</html>

Open in new window


This solution blocks the following:
Clients revisiting that particular part of the site (e.g. just typing in their address bar: sendmoney.php?submit=1 )
Clients reloading the critical stage of the page
Clients using their browsers back / forward history

It is also completely serverside and as such will work with any browser your clients may use.

I hope this solution helps you and thank you for reading!
1
Comment
Author:Midboss
0 Comments

Featured Post

Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

Join & Write a Comment

The viewer will learn how to dynamically set the form action using jQuery.
The viewer will learn how to count occurrences of each item in an array.

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month