Jump to content
jodunno

browser history broken after form submit

Recommended Posts

Dear fourm, i am wondering if anyone can shed some light on this form submission problem with back/forward buttons.

long story short: i used to make a form, then submit to a form process php file. errors were difficult to deal with. Thus, many people tell me to submit to the same page. I now submit to the same page.

my homepage has a login button. i use a csrf token in a hidden input matched with a session variable. i decided to submit to same page and handle the submit like so:

if server request-method = post and isset input name and isset session token then check the token with hash_equals

if everything matches then show the login page.

i don't have a problem with the form and the form submission processing. everything i fine.

now when i use the browser refresh button on the login page, i am sent back to the homepage. all is good.

when i press the back button, i go back to the homepage again. super.

then i press the forward button in the browser and i get a not connected error.

the back button now also shows this error.

i tinkered around a bit and added crazy ideas and it worked one time with unset($_POST) as an else to the if mentioned above.

the idea is that if server request-method is post without an else. the page is listed below for any get request to process.

i assume that the browser is trying to repost data is empty but my if statement should kick it out to the get code, right?

what i want is to unset the csrf token and the matching session token for security purposes. i suppose that this breaks the back/forward buttons?

how can i get the browser to show the homepage regardless of the post situation? there must be a logical answer. something in my code is breaking this an preventing the browser from simply loading the home page

any help is greatly appreciated! Thank you.

Share this post


Link to post
Share on other sites
Posted (edited)

Maybe it's too early in the morning, but that was hard for me to follow.

If the problem is the browser complaining when you go back/forward then your problem might be solved by using a redirect: when the login is successful, issue a redirect to the homepage (or wherever). The back and forward buttons in the browser should work normally.

If the problem is something about always showing the homepage then... I think GPR PRG will solve your problem too, otherwise I'm not entirely sure what you want.

Edited by requinix

Share this post


Link to post
Share on other sites

Hello requinix, i agree. i am so mentally tired today. I am not a programmer but i am trying to build a subscription-based website. I've managed to figure out how to build a login with sessions and a database. I am struggling to understand how to design this app correctly. I want a login button on my homepage. When one clicks the login button, the form will submit to self action="". Inside the index.php file, i check for a request-method == post and that the login form was submitted. I have another form on my homeppage as well (subscribe). I was hoping that i could handle both form submissions in the same page (index.php or homepage). Thus, i would add another if request-method == post and input name = subscribe instead of login. Now i wonder if i should just route you to a login page and do it all in that page instead. Because once i detect the login button was clicked, then i need to load a login form page. Then, i will need to handle/process that form also in the same page. Maybe this is not how it should be done.

should i just route the user to a /login directory instead? will this solve the back/forward button issue?

i hope that this makes sense. Let me try one more time:

homepage or index.php has two forms: login and subscribe. Both forms submit to index.php (action=""). I use if blocks to detect which form is submitted. I also use csrf tokens with both forms (both tokens are different and randomly generated per page request). I make sure that the tokens are correct before loading the appropriate page (if all is well then require 'login.php' and if all is well with form two then require 'subscribe.php). If i am submitting to the same file and including new files/forms, then is this the problem? should i be submitting the forms to a separate file?

Share this post


Link to post
Share on other sites
8 minutes ago, jodunno said:

Hello requinix, i agree. i am so mentally tired today. I am not a programmer but i am trying to build a subscription-based website. I've managed to figure out how to build a login with sessions and a database. I am struggling to understand how to design this app correctly. I want a login button on my homepage. When one clicks the login button, the form will submit to self action="". Inside the index.php file, i check for a request-method == post and that the login form was submitted. I have another form on my homeppage as well (subscribe). I was hoping that i could handle both form submissions in the same page (index.php or homepage). Thus, i would add another if request-method == post and input name = subscribe instead of login. Now i wonder if i should just route you to a login page and do it all in that page instead. Because once i detect the login button was clicked, then i need to load a login form page. Then, i will need to handle/process that form also in the same page. Maybe this is not how it should be done.

Probably not how it should be done.

For one, only one form can be submitted at a time. You can do a login form and you can do a subscribe form, but if you wanted both then you'd have to combine everything into one single form. Which you could do.

Except that doesn't make sense. Subscribe and log in? Subscribe means the user isn't subscribed already, and log in presumably means they are subscribed, so how can you do both? Now if you wanted to subscribe someone and then log them in after, that's different (and quite reasonable, if not expected).

So keep the two separate.

8 minutes ago, jodunno said:

should i just route the user to a /login directory instead? will this solve the back/forward button issue?

You should have a dedicated login page. Something that can be linked to, but more importantly somewhere that you can display errors if the login doesn't work. It will also address a particular problem: trying to log in from a page that was POSTed.

The normal way to deal with this concept is to make your login form submit to /login. Not the same page. You can use a hidden input with the current URL so the login page can redirect back to it if/when successful. It also means you have more control over the process in the case of pages that shouldn't return, with code that might look something like

<form action="/login" method="post">
  <input type="hidden" name="csrf" value="<?= $csrf_token ?>">
  <?php if ($_SERVER["REQUEST_METHOD"] != "POST" /* disabled on POSTed pages */ && empty($DONT_RETURN_AFTER_LOGIN) /* disabled when undesired */) { ?>
  <input type="hidden" name="return" value="<?= htmlspecialchars($_SERVER["REQUEST_URI"]) ?>">
  <?php } ?>
  ...
</form>
<?php

...
  
// after logging in
if (isset($_POST["return"])) {
  // strip out anything besides the URL path and query string
  $parsed = parse_url($_POST["return"]);
  $return = $parsed["path"] ?? "/";
  if (!empty($parsed["query"])) {
    $return .= "?" . $parsed["query"];
  }
} else {
  // default
  $return = "/";
}

// redirect
header("Location: " . $return);
exit;

 

Share this post


Link to post
Share on other sites

Hello requinix, i will post my original code, which works but does the same thing. If i refresh the page, then i see the login but back/forward button show not connected errors in Edge browser atleast. When i see the error pages using back/forward buttons, then a refresh takes me back to the index/login page. However, i am fighting to stop the page not connected error altogether. i suppose that i can live with it if it is a problem because i also set no cache must revalidate headers. Here is my original code:

/login/index.php
<?php
session_start();

  if ($_SERVER["REQUEST_METHOD"] == 'POST' && !isset($_SESSION['MemberId'])) {
      require '../../Above_Root_Dir/login.php';
    session_write_close();
  }

  if (isset($_SESSION['MemberId']) {
      require '../../Above_Root_Dir/welcome.php';
    session_write_close();
  } else {
      require '../../Above_Root_Dir/loginForm.php';
    session_write_close();
  }

exit;
?>

Above_Root_Dir/loginForm.php

<?php
  //code to create a $CSRFtoken hash_hmac() etc
  //$_SESSION['token'] = CSRFtoken;
?>
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>

    <form autocomplete="off" accept-charset="UTF-8" method="post" action=""><input type="hidden" name="CSRFtoken" value="<?php echo $CSRFtoken; ?>" />
      <div>Login</div>
      <div>
        <div><input type="text" id="username" name="username" placeholder="username" required /></div>
        <div><input type="password" name="password" placeholder="password" required /></div>
        <div><input type="submit" value="Login" /></div>
      </div>
    </form>
    [also show errors in this login div area]

</body>
</html>
<?php exit; ?>

Above_Root_Dir/login.php
checks credentials then sets session variables etc
redirect to /login/index.php to see errors or a welcome page

should i stick with this design? i decided last week to try to make it all submit to the same page.

one other thing that you should know: i don't allow direct access to my login page. I have no hyperlinks pointing to login. i use a form with a submit button labelled "Login" on the homepage. I do this to stop abuse of the form. If you have no token with a valid time expiration, then header relocate to my homepage. Maybe you have time to look at my posted code and offer an opinion. I always value your opinion. I've had you respond to several of my posts before. I remember you and i trust your opinion. You are a good programmer. Thank you, requinix.

Share this post


Link to post
Share on other sites
4 minutes ago, jodunno said:

then i see the login but back/forward button show not connected errors in Edge browser atleast

Exactly what does it say?

Share this post


Link to post
Share on other sites

I use xampp for development and my default browser for testing is Edge but i also test in Firefox, Google Chrome and Opera. I get the same results across browsers. Edge shows a white page with a broken heart image and the following text:

You're not connected

And the web just isn't the same without you.

Let's get you back online

ul list with network troubleshooting tips

[Help me fix it] button.

If i refresh the page at this error, then the index.php shows just fine.

Share this post


Link to post
Share on other sites

That "friendly" error doesn't actually help tell you what the problem is. What's the error code? What do the other browsers say?

Share this post


Link to post
Share on other sites

I don't consider this problem to be a coding error. I think that this has something to do with one of the following concepts (and i know not enough to deduce which one is a culprit): I use headers to control the cache:

header("Expires: on, 01 Jan 1991 00:00:00 GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

I check if the request method is post and my form field is set, then inside this if block is another if block checking for token ttl match, then another if block with hash_equals to check the CSRF token then unset the session variable and form token, then unset the $_POST key value to eliminate reentering the page.

I assume that if server request method is post is not true beause we use the back button or forward button, so i also assume that the header redirect will be used. Maybe i assume wrong and the back/forward button resubmits to the page but it is now empty, so the page breaks? I must be doing something wrong which is causing the browsers to not load a page. My goal is to prevent someone from accessing this form without using my form with a token, a token ttl and correct data. I guess that the mission is accomplished but since i am not an experienced or very good programmer, then i wonder if something is wrong with the way i am addressing form handling. I was hoping that someone would be able to pinpoint a cause of this seemingly lost connection.

Share this post


Link to post
Share on other sites

The Expires header is the wrong syntax. It needs to be just a date - remove the "on,".

Unless the browser is instructed otherwise and/or it decides for itself not to cache the page, back and forward tend to go from its cache. It's faster. Using anti-caching headers like that combined with POST should make sure the browser never goes from the cache.

But this still doesn't address the "connection" problem. What is the HTTP status code and what do the other browsers do or say?

Share this post


Link to post
Share on other sites

i just copied and pasted headers from a google result instead of typing. my code is on my other computer and i didn't want to restart it. sorry. I didn't see the 'on' in that code. I don't type the expiration with on in the header.

i am in Europe and it is after midnight. I need to sleep soon so tht i can get up at six am. i've only slept five hours a day now for a week. I am getting very tired mentally.

I tried in firefox and i still get a blank page but firefox says that the document has expired and is no longer in the cache. a try again button just refreshes the page and i get my index.php to show. However, i went crazy in both edge and firefox pressing back/forward back/forward then back,back,back,back and the last back was looking for google.com in both browsers. This is strange. It seems as though the connection to xampp is lost and my browsers look for an ethernet or wireless connection. Maybe this doesn't happen on a live server?

if i can find some time tomorrow, i will write a simple form and form process page to mock my design. Then maybe you can try the files in xampp to see what happens when you use the back/forward buttons. Be warned, i am cuckoo about security so i use an sha3-512 csrf token. I refuse to go lower. I'm just saying that alot of people cry boo-hoos that it is overkill but i'm not changing it. So when i post my code just ignore it please. I like my tokens powerful.

 

I'll be back tomorrow...

Goodnight and i hope that you have a happy, productive, safe and pleasant evening.

Share this post


Link to post
Share on other sites
Posted (edited)

Good morning, i will try to start working on the form files but i have an appointment today, so i must leave soon. I will be back later today with some code.

Meantime, i noticed some code in your post earlier:

$return = "/";
}

// redirect
header("Location: " . $return);
exit;

I recently learned that double quotes allow variables to be executed, so you don't need to concatenate a variable.

$return = "/";
}

// redirect
header("Location: $return");
exit;

I now prefer to code php with single quotes for security purposes. I certainly do not want to make a mistake and enter a variable into double quotes. I've allready done that, which is why i know that it will be executed.

okay, i will start working on the files...

edit: after my discovery, i understood why the double quotes are used in a pdo connection (to execute the variables). I really wonder how many books and tutorials mention this fact. No wonder i am struggling with forms. programming appears to be a serius trade secret protectionism market. noone wants to tell anyone how to accomplish something without money. however, i also read a recent study that hired programmers for money to see if they write secure code or not. 99% of the results were insecure code that appears to have been taken from the web, such as github. I find it intresting that authors want us to pay for books that leave out tons of important info. I remember all of the books i read in the 90s about c++ were all about console apps. i wanted to make windows with buttons. not a single book mentioned one word: win32api. Unbelieveable waste of time and money! Anyway, if anyone is a newbie, like me, then remember that doublke quotes will execute a variable.

Edited by jodunno
added tip for newbies like me

Share this post


Link to post
Share on other sites

one more tip for newbies: i forgot to mention that double quotes also take longer to execute because php checks for variables, so when you want to optimize your code, then switch to single quotes.

Share this post


Link to post
Share on other sites

okay, so i've created some mock files for testing. The heirarchy is simple:

htdocs contains index.php and a directory named login with an index.php.

above the root (../) is a directory named lockbox which contains functions.php and loginpage.php

i tested this and it also shows no connection and bo error. where or why do you think an error should be shown? i really think that this is because i delete the token. I have no idea what to put in the file that instructs the browser to return to the index page (other than a header relocate). As one should know, you should not leave these tokens available for reuse. It is very easy to reuse the token on the same network with a spoofed ip and wireshark. This i know for sure, so let's not debate good security practice. I really need to know of a method that instructs the browser that the page is now inaccessible, so return to the index page instead. I have no idea how to do this. Perhaps it is as simple as restructuring my if block to only check for post, then use a second if block to check for hash_equals. I don't see how this makes a difference because i think that it is evident that it is an http request-response/cache issue. I don't know how to fix it. How can i still process these forms and handle the tokens without the connection being lost?

htdocs = index.php

<?php
session_start();

require dirname(__FILE__) . '/../lockbox/functions.php';
$LoginPageToken = createCSRFtoken();
$_SESSION['LoginPageToken'] = $LoginPageToken;

  header('Content-Type: text/html; charset=utf-8');
  header('Expires: Sat, 01 Jan 1991 05:00:00 GMT');
  header('Cache-Control: no-cache, no-store, must-revalidate');
  header('Cache-Control: post-check=0, pre-check=0', false);
  header('Pragma: no-cache');
?>
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="x-ua-compatible" content="IE=edge" />
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; worker-src 'none'; connect-src 'self'; style-src 'self'; img-src 'self'; media-src 'self'; font-src 'none'; plugin-types 'none'; frame-src 'none'; child-src 'none'; object-src 'none'" />
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=0.5, maximum-scale=2" />
  <meta http-equiv="window-target" content="_top" />
  <meta http-equiv="X-Permitted-Cross-Domain-Policies" content="none" />
</head>
<body>

<div><div>
  <ul>
    <li><form autocomplete="off" accept-charset="UTF-8" method="post" action="/login/"><input type="hidden" name="LoginPageToken" value="<?php echo $LoginPageToken; ?>" /><input type="submit" name="LoginPage" value="Login" /></form></li>
  </ul>
</div></div>

</div>
</body></html>

htdocs /login/ index.php

<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty(isset($_POST['LoginPageToken'])) && !empty(isset($_SESSION['LoginPageToken']))) {

      if (hash_equals($_SESSION['LoginPageToken'], $_POST['LoginPageToken'])) {
           unset($_SESSION['LoginPageToken']);
                header('Content-Type: text/html; charset=utf-8');
                header('Expires: Sat, 01 Jan 1991 05:00:00 GMT');
                header('Cache-Control: no-cache, no-store, must-revalidate');
                header('Cache-Control: post-check=0, pre-check=0', false);
                header('Pragma: no-cache');

                require dirname(__FILE__) . '/../../lockbox/loginpage.php';
                exit;
      } else {
            header('Location: /');
            exit;
      }
}
header('Location: /'); exit;
?>

../lockbox/ functions.php

<?php

function createCSRFtoken() {
  $unique = session_id() . bin2hex(random_bytes(32));
  $Key1 = base64_encode(random_bytes(64));
  $Key2 = base64_encode(random_bytes(64));
  $preToken = hash_hmac('sha3-512', $unique, $Key1, TRUE);
  $Token = hash_hmac('sha3-512', $preToken, $Key2);

  return $Token;
}

?>

../lockbox/ loginpage.php

<?php 
  require dirname(__FILE__) . '/../lockbox/functions.php';

  $LoginToken = $_SESSION['LoginToken'] = NULL;
  $LoginToken = createCSRFtoken();
  $_SESSION['LoginToken'] = $LoginToken;

  header('Content-Type: text/html; charset=utf-8');
  header('Expires: Sat, 01 Jan 1991 05:00:00 GMT');
  header('Cache-Control: no-cache, no-store, must-revalidate');
  header('Cache-Control: post-check=0, pre-check=0', false);
  header('Pragma: no-cache');
?>
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="x-ua-compatible" content="IE=edge" />
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; worker-src 'none'; connect-src 'self'; style-src 'self'; img-src 'self'; media-src 'self'; font-src 'none'; plugin-types 'none'; frame-src 'none'; child-src 'none'; object-src 'none'" />
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=0.5, maximum-scale=2" />
  <meta http-equiv="window-target" content="_top" />
  <meta http-equiv="X-Permitted-Cross-Domain-Policies" content="none" />
</head>
<body>

  <form autocomplete="off" accept-charset="UTF-8" method="post" action=""><input type="hidden" name="LoginToken" value="<?php echo $LoginToken; ?>" />
  <div>
      <div>Login</div>
      <div><div class="browserContainer">
        <di><input type="text" name="username" placeholder="Username" required /></div>
        <div><input type="password" name="password" placeholder="Password" required /></div>
        <div><input type="submit" value="Login" /></div>
      </div></div>
  </div></form>

</body>
</html>

i really don't know how this is not a common problem amongst newbies. Certainly, someone should have a resolution. I find it strange to have to post this code for the problem to be understood. Simply install the files into xampp. load index.php, then click the login button. at the loginpage.php: click the back button then the forward button. Connection broken at the forward to loginpage.php. I think that something must be placed in this file to prevent the connection breaking. When we click back then forward: what is really happening? is the form being resubmitted? or is the browser checking the cache? i use no cache headers, so i am confused as to why this is happening. Perhaps someone can offer a resolution.

Share this post


Link to post
Share on other sites

Hello, ie11 and recent version of FF show cache document expired messages (which is normal). I searched Google for this problem and found a nice article with explanations:

http://shiflett.org/articles/how-to-avoid-page-has-expired-warnings

i guess the browser is simply following instructions, thus, i need to hide the page from the history.

Share this post


Link to post
Share on other sites

Let me say this:  Double quotes are not responsible for a "variable to be executed".  YOU are totally responsible for any "executing" of variables.   Using double quotes rather than single quotes allows for a variable to be "interpreted" as in the following:

$myvar = 'abc';

echo 'Myvar is now $myvar';

This will produce an output of :   Myvar is now $myvar

Using this string:

echo "Myvar is now $myvar";

will output:  Myvar is now abc

which is what you usually want to have happen.  There is no "executing" going on here.

You use double quotes (or no quotes at all!) in order to have the contents of a variable read or interpreted properly.  Many beginners run into this problem (single or double?) before they realize the difference.  One rarely uses single quotes around php vars unless they are already wrapped inside another (outer) pair of double quotes.  For example:

echo "Myvar is now '$myvar' "

will output the expected value of:  Myvar is now 'abc'

simply because the outer pair of double quotes takes precedence over the single ones surrounding the var.

Of course you can skip using the quotes by using concatenation of your strings but many times it is easier to read the code when strings are properly using the correct quotes.

Share this post


Link to post
Share on other sites

so i have kept the headers but i've implemented a header redirect to avoid the history. All is good with refresh, back and forward.

i think that PRG is really the solution, so thank you, once again, requinix.

On 4/8/2019 at 5:50 PM, requinix said:

Maybe it's too early in the morning, but that was hard for me to follow.

If the problem is the browser complaining when you go back/forward then your problem might be solved by using a redirect: when the login is successful, issue a redirect to the homepage (or wherever). The back and forward buttons in the browser should work normally.

If the problem is something about always showing the homepage then... I think GPR PRG will solve your problem too, otherwise I'm not entirely sure what you want.

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.