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.
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).
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
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.
Related
I have a php sign up process for uses. When the user gets to the second last page of the sign-up process, it shows a summary of all the signup details and the submit button(Confirm and Pay button basically). Now when the user clicks this button, all those details gets posted to the sql database and that user is created in the DB with a username and password. Now the problem is, if you click that submit button more than once, it resubmits the data and creates duplicate entries in the database, where each duplicate entry has the same details(name, email etc), but different usernames and passwords.
How do I prevent this? I know you can submit and postback to self right after but how do I do that exactly? I tried to implement this code but it does not work: http://www.bjw.co.nz/developer/general/75-how-to-prevent-form-resubmission
Thanks
I would advise a bit of JavaScript in this situation. Add the following to the submit button:
onclick="this.disabled=true;"
This will disable the button once it is clicked so that it can't be clicked again.
You should save a value into the PHP session that identifies if the form is already submitted, using $_SESSION. The PHP Session is locked while each request carries out, so when the first submit locks it, the second will have to wait until the first submit is finished and has set the value. The second request will afterwards read it, and break upon the value being set to already submitted.
Here's an example of how this could be done. It could probably incorporate bugs if one user is doing several registrations at once (why should he?).
On the page that contains the submit button of the form, you should set a marker:
session_start();
$_SESSION["RegistrationSubmitted"] = false;
This says that the registration that a user is currently doing has not been submitted yet.
On the page that is being submitted (POSTed) to, you can check this value:
session_start();
if (isset($_SESSION["RegistrationSubmitted"]) {
if ($_SESSION["RegistrationSubmitted"] == true) {
die("Registration has already been submitted!");
} else {
// Process registration here
$_SESSION["RegistrationSubmitted"] == true;
}
} else {
die("Last registration page hasn't been reached! (A bot?)");
}
Aside from messing with the PHP code or the DOM, why not modify your database table to use unique keys. This way regardless of how many times the user clicks a submit button, the database simply wont care.
This may help you: “INSERT IGNORE” vs “INSERT … ON DUPLICATE KEY UPDATE”
You could create a form key like:
<input type="hidden" name="form-key" value="<?php echo $last_order_datetime; ?>" />
Then, when a new order is submitted, check if the value of $_POST['form-key'] is exactly equal to the most recent order stored.
If it is, process the form.
else, it is a resubmit or some other attempt.
Is there a way to ensure the $_POST data my code received came from my form and not an outside influence. Basically I don't want someone to be able to spoof a $_POST to a universally available page such as account creation. The account creation page is accessible by any user, but I want to ensure only the data submitted by my account_creation form is what gets processed.
The only thing I could think of was initiating a $_SESSION, and then supplying the session_id to the form using a hidden input. Upon $_POST the value of the hidden input would then be matched against the current session_id.
If there is a better method to achieve this result? If there is I look forward to hearing it.
You cannot ensure that data came from a form. A POST request is just a POST request, it can be generated in any number of ways. An HTML form is just one of those ways that's very user friendly. Your server needs to validate whether the data received via the POST request is valid or not and whether to act on it or not.
Having said that, there are things that can help you to restrict and validate the data that is being submitted. First of all, require that a user is logged in using (session) cookies. That eliminates random requests by anonymous users. Secondly, you can embed a token as a hidden field into the form which you also save into the user's session. The POST request needs to contain that token in order to be valid. The token is simply a pseudo-random string.
You can enhance this by preparing a hash of the form fields that you expect the user to submit. If the form value should be read-only, you can include the value into the hash as well. E.g.:
$rand = md5(mt_rand());
$hash = sha1('lastname:firstname:email:' . $rand);
$_SESSION['rand'] = $rand;
$_SESSION['hash'] = $hash;
// on form submit:
$keys = array_keys($_POST);
$checkHash = sha1(join(':', $keys) . ':' . $_SESSION['rand']);
if ($checkHash != $_SESSION['hash']) {
die('Form submission failed token validation');
}
That's just a quick example, you'll probably want to sort the keys alphabetically to make sure you'll get the same hash etc. It demonstrates the concept of the user needing to have a unique token for each request though which prevents tempering with forms and submitting more or less data than wanted.
This still does not mean that a user actually used your form to submit the data though.
$ref = $_SERVER['HTTP_REFERER'];
if($ref !== 'some site path/index.php')
{
die("Access Denied!");
}
This should prevent most people from posting data to your database from an outside influence.
Slightly better is to add additional validation such as user_agent, user_ip and some other $_SERVER vars - those are the two I use.
So, create the unique ID (or Session ID) as you describe, but add a little extra validation that the agent and ip also match. Not fool proof, but adds another little layer of security.
Edit: I should add that you don't send the user agent back; keep that server side and silently validate against the returned session id.
Also, if a submission fails validation, never reveal that back to the user as to why - that way a cheat doesn't know how your tracking them. You can also add "5 invalids and you're out" tracking, but you need to sort of login for that.
Using the session ID is certainly one way of doing it. But there are other options most (if not all) of which involve adding some data as a hidden field.
Use a CAPCHA. That will always be unique to each page load and therefore make it mandatory to use your form.
Generate random data and store it in the DB (or just the $_SESSION variable) and check it once the form is submitted.
Option one is the one I recommend for a user creation form as it pulls double duty. It stops automated submission of your own form, while ensuring that the $_POST data is coming from your own form.
This is a standard pattern pattern to prevent XSRF. Essentially it is the similar to what you mentioned. Server creates a random token when form is rendered for the user. It is tied to a browser cookie for the user. On form submission it is posted back to the server. Server then compares the token with what was issued and form action is performed only after a successful match.
There's a lot of good mentions of putting a unique value in the form and matching to the stored value in the server side session. Do that, but also think about what happens when a user uses the back button and possibly tries to submit the form twice, or they open a second browser window(same session!), or they use multiple forms on your site.
Don't create crazy bugs by not thinking your system through.
I have tried:
the POST/REDIRECT/GET method, but this does not protect against multiple instances of the same form (e.g. if a user opens form.php in two separate windows, and submits the form in window 1, they can still submit in window 2)
the UNIQUE TOKEN method, where a uid is generated into a session variable when the form is loaded and confirmed and unset when the form is processed, but if the user has other different forms open, the variable is unset from these other forms as well so they get treated as 'already processed'.
Have you seen the comment from user "Ofir Baruch"? It seems correct to me.
You just need to combine the two approaches you have already tried, make it correctly, and, as Ofir baruch said, have a unique token session for each form.
More or less like this:
form1.php
session_start();
if (empty($_SESSION['form_tokens']['form1']))
{
$_SESSION['form_tokens']['form1'] = generate_random_token();
}
if (isset($_POST['token']))
{
if ($_POST['token']) != $_SESSION['form_tokens']['form1'])
{
// the token is invalid - do not process the form
redirect('/some_page');
}
else
{
// process the form here
if ($success)
{
// reset the token for this form
unset($_SESSION['form_tokens']['form1']);
redirect('/another_page');
}
}
}
<form id="form1">
<input type="hidden" name="token" value="<?php echo $_SESSION['form_tokens']['form1']; ?>" />
<input type="submit" value="Submit" />
</form>
In form2.php you would do the same, but using its unique token instead:
$_SESSION['form_tokens']['form2']
If you want to prevent ALSO using two different browsers - or even computers - and this is so important, then you should deal with it at another place - I mean, you should not allow that a same USER can have TWO SESSIONS. There are several ways to accomplish this, but it is another question.
Are the form submissions limited to logged-in users or site visitors?
If this form is only for logged-in users, you can check their user_id as soon as the form reaches your controller. You could set a temporary cookie or session variable while the form processes. When the form completes, you can unset this session variable. As soon as the form submits, check to see whether this session variable is ALREADY set (i.e. whether or not they've sent the form twice). If this session variable is detected, fail the request.
I suppose you can do the same with site visitors, or even resort to using tables to store IP addresses, but that's a tad excessive and resource intensive.
Identify which forms you want single submissions only. Then add your unique token as hidden id, for each time you generate form html and store in session. This was you know all valid IDs that are part of the unprocessed group. just one submission cancels all other IDs too of that group ONLY. this also means, when he opens few other forms after that, they start creating new group.
this open needs you to have a session array (or db) with items of all IDs that belong to unprocessed form. only one group can remain at a time. any ID not in the group is ignored/cancelled.
I have this bit of script:
if (isset($_POST['comment_posted'])) {
$user_comment = mysql_real_escape_string($_POST['user_comment']);
$add_user_comment = Event::addUserComment($id,$user->user_id,$user_comment);
}
After a user submits his comment, and refreshes the page, he is being presented with the "you are going to resend the post data" warning. And if the user accepts, it will re-insert the user comment.
I understand that I can prevent that by adding using the header function and redirect the member to the same page. Is it possible to solve this issue without redirecting the member?
No. You'll either do a post-redirect-get or subsequent refreshes will present this dialog to the user.
In case you chose not to do a PRG, you need to somehow detect that the submission is duplicate. One easy way is to have injected a hidden parameter with a random hash/number (e.g called token). Upon submission you'll have to check that the token you expect (which you'll have probably stored in the http session) is being sent together with the other POST parameters. On valid submission you'll remove/invalidate this token. That way when a POST comes which a non recognised token then it's most probably a duplicate or out of date request.
If you implement this correctly then you'll also make your application proof to csrf attacks.
You could set some session variable after successful submission. For each submission you check whether the variable is set or not, on you make an insertion of data.
I'm developing a PHP-MySQL app that enables registered users to enter text comments. Problem is:
User sign-in into the web site - OKAY
User presented with a form to submit text comment - OKAY
User enters text comment and submits - OKAY
I have a routine that sanitize the comment & save it into MySQL (with userid, textcomment, datetime stamp) & present back the user that his/her comment is entered - OKAY
User decides to refresh browser - a duplicate comment is entered - BAD!
I was thinking 3 options:
OPTION 1: Routine that checks: last time user posted comment, and if so, check if this is a duplicate. If duplicate then display error message.
OPTION 2: Routine that does not allow a user to post too quickly. So basically do not allow postings of comments within 1 minute or so. So if browser is refreshed the comment will be ignored.
OPTION 3: Manipulate the browser cache to clear out its contents so when refreshed no duplicate will be entered.
Now in the context of my application, my concerns with OPTION 1 and OPTION 2 is performance PHP-MySQL since I already have various queries within the same page that push/get data from databases. So OPTION 3 may target the issue differently.
Questions is: If I go for OPTION 3 can this be considered a Best Practice? meaning clearing the browser cache is the best most effective solution? I have read that there are consequences too? your thoughts are appreciated!
Just do a redirect after submitting data to the database. It's a common practise.
An http redirect instructs the browser to issue an http GET for the url specified (as opposed to the http POST that is used to submit the form) . If you do this right after you have inserted data into the database, when the user refreshes his browser nothing will happen other than him seeing the same page again.
This question on SO tells how you redirect with php.
Just unset the posted variable after you have inserted it in the database
<?php
if(isset($_POST["comment"])){
//store in the database
//on successful storage
unset($_POST["comment"]);
}
?>
Thus the value won't be posted back when user refreshes the page...
You need to implement the Post/Redirect/Get pattern.
I would use Option 4: Use a one-time form token that authenticates the form request. If the request was successful, invalidate the token.
When doing this, even returning to the form after a redirection won’t allow it to send the form again. Additionally, this will also make CSRF attacks harder.
After entering data into database redirect the page using header or location.href
i have found new way try this.
function PreventResendData($invalidpage='index.php'){
if( $_POST && !is_array( $_SESSION['post_var'] ) ) {
$_SESSION['post_var'] = $_POST;
header('location:'.$_SERVER['PHP_SELF']);
}
else if($_SESSION['post_var'])
{
$_POST = $_SESSION['post_var'];
$_SESSION['post_var'] = '';
}
else
{
header("location:".$invalidpage);
}
}