How to prevent unauthorised login by resubmitting page

Hi
I have a login form with username and password which uses if($_SERVER['REQUEST_METHOD'] == 'POST'){ to determine if data has been submitted and then either display form or proceed to verification / authorisation and process login.

My problem is, even when login session has timed out or user has logged out, someone could simply click on ‘back’, resubmit, and the system will login again using the previously submitted credentials.

I assumed using if($_SERVER['REQUEST_METHOD'] == 'POST'){ would display the form again and not proceed to the verification and authorisation if the form had not actually been posted, but it seems to still register that form has been posted and accept the previously entered credentials.

This is obviously a security issue.

I cannot use a session var because the session is destroyed when the user logs out or times out, I don’t want to use javascript since that could be bypassed and I can’t store a flag in the database because if the user closed incorrectly the flag would still be set.

How can I overcome this and prevent someone refreshing / reloading and using previously entered credentials to log in again.

Basically if the page is reloaded I want them to have to complete the form again.

Thanks in advance

Only way I can think of is to generate a unique token for each login form, and if that token has already been used, reject the login and force the user to re-enter the credentials.

Not sure it’s really necessary unless you’re dealing with financial or PII. Seems a bit like overkill, but that’s MHO. I use that kind of functionality all the time - go back and resubmit the login if I get called away and timed out.

More important would be to ensure cookies don’t get left behind on a public browser.

But it doesn’t seem like overkill to me, seems like a real security risk. A user logs out, walks away, someone else clicks back and has access to all his personal info

Unfortunately, this is due to the browser caching the form submission, which you cannot change, so, you must handle this elsewhere.

Do the following -

  1. Add autocomplete='off' to the form tag. This will prevent the browser from showing and offering to fill in from a list of previously entered values in text fields.
  2. Upon successfully completing the post method form processing code, perform a redirect to the exact same url of the current page to cause a get request for that url. This is known as the Post, Redirect, Get (PRG) pattern. This will ‘register’ a get request for the last submission to that url and will prevent the browser from resubmitting the form data for the last submission, but not for prior submissions in the back-button history (this would occur when there were multiple submissions of the same form, such as would occur due to validation errors.)
  3. Use a run-once token, that requires the form page to get requested to generate each token, which is stored in a session variable, then tested and cleared in the form processing code. This will also prevent duplicate/multiple accidental form submissions. This will require that you ‘modernize’ your logout code so that you aren’t destroying the entire session, only unsetting the session variable that is remembering the who the logged in visitor is.

If the user’s logged out, and goes back to the page, why would your page contain their personal info?

This isnt resubmitting a form… you’re talking about a dynamically loaded page.

If the user’s logged out, you should have destroyed their session. If their session doesnt exist, the server, even if it receives form data, doesnt have a user to associate it to, because you destroyed the session. So the form handler would throw an error.

If you’re talking about browsers caching form-field information, thats what mab’s autocomplete tag is preventing.

@mabismad Thanks for the detailed response. the autocomplete = 'off'' but it never gets as far as presenting the form again, it just re-submits the values previously entered.

I thought I was using PRG pattern by using if($_SERVER['REQUEST_METHOD'] == 'POST'){ and setting the form action so when submitted the script calls itself. But this does not seem to register a get request for the last submission and when I click back and refresh it continues as if the form has been resubmitted with another post action and it simply resubmits the form data again.

That’s what is really confusing me, it is acting as if the form was submitted again, even though this time it was not

The page doesn’t contain their personal info, but going back resubmits the login info again and logs back in. Then the new ‘user’ has access to all their info

Yea, you definitely shouldnt leave autocomplete on for a login form.

I understand that but it is never getting as far as displaying the form again, that would be ok, the problem is it re-submits the user / pass that was previously entered and logs back in again

@mabismad is correct, you need to implement PRG. When done correctly you will not have the problem.

If you are able it would be helpful if you could put your project on Github so we can review it as a whole.

Is your form and form processing on the same page? The PRG pattern only prevents the browser from trying to resubmit the form data when the URL of the form and form processing are the same.

Yes it is all on same page but also uses a couple of includes, which should not affect it. I am going to look at a simple PRG and get that to work and then gradually introduce my additional functionality. It will be a good exercise and learning opportunity, and once I have a simple PRG that works, I can then expand it and if it fails I will know where. I will be back if I have any issues - thanks again guys !

OK so I am obviously not grasping something correctly. Below is my attempt at a basic PRG pattern. My hope was that initially the form will be displayed (which it does) but that after clicking ‘Continue’ and ‘logging in’, when refreshed, the processing code would not execute and the form would display again.

However when I refresh it behaves as if another POST was initiated and displays the previously entered data and not the form.

I can get the script to behave how I want if I redirect on success - header("Location: welcome-screen.php"); then, clicking back reloads the form, but I was told using this method is bad and I should try and keep relevant code within the same script. However, it is the only solution I have found that works. And in fact this would seem to me to be the same as setting the form action to a welcome page, which I was also told to avoid.

Research I have done regarding PRG tell me I need to use the PHP header to redirest and suggest as follows -

header("HTTP/1.1 303 See Other");
header("Location: http://$_SERVER[HTTP_HOST]/echo.php");

and now I am really lost!

(By the way - I know this is not secure and far from complete, but I am just trying to get the very basic concept of PRG working first)

What am I misunderstanding here ?

Thank you

<?php
if($_SERVER['REQUEST_METHOD'] == 'POST'){	
	echo 'Welcome - you are logged in as - <br>';
	echo 'Username entered was - '.$_POST['username'].'<br>';
	echo 'Password entered was - '.$_POST['password'].'<br>';	
} else {	
?>	
<!DOCTYPE html>
<html lang="en">
	<head>
	</head>
	<body>
		<header>
		</header>
		<!-- Content here -->	
		<h2>Please Login !</h2>
		<form id="contact-form" method="post" action="basic-prg.php" name="form1"> 								
								
			<div>
				<label>User Name - </label>
				<input type="text" name="username"> 
			</div>							
			<div>
				<label>Password - </label> 
				<input type="text" name="password">
			</div>							
			<button type="submit" name="submit" class="btn btn-skin pull-right btn-success">Continue</button><br>
		</form>		
		<!-- End Content here -->	
		<footer>
		</footer>
	</body>	
</html>
<?php 
} 
?>

How would I do that please - header("Location: MyCurrentPage.php"); ?

I use the following universal method -

die(header("Refresh:0"));

Note: every redirect needs an exit/die statement to stop php code execution. A header() statement doesn’t do that because a header() is just a way of sending raw http headers to the browser, which could be any valid header, not just a redirect.

1 Like

This tutorial will get you on the right track.

Thanks to all (as always) good advice I think and I have a few options to explore. Many thanks again.

Just wanted to add that anytime you POST something you should always redirect after processing the request. Not just login forms. There are very few use cases where you want the user to be able to repost by just backing up in the browser.

Thanks for the add on. I mistakenly thought that was what I was doing by checking if form had already been posted once. I didn’t appreciate the need to redirect and force a GET to prevent data being RE-POSTED.

Eek!

“Good code”

if (isset($_POST["submit"])) {
...
$word = $_POST["word"];