Jump to content

How to make a secure login and session


Dakkadakka

Recommended Posts

I'm working on a shopping cart that calls for a secure login and certain data fields being kept as users move page to page. This is very important because the beta version of the site gives discounts on the individual user level, and the first version of this site will be exclusively for customers who have a history with my company. It won't be for everybody until we're good and ready to expand. It's just for the customers who want to be able to order things from their computer for now. When I did my senior design page, it had a very superficial login with a user name and password, and if you knew the URL, you could just type it in. Now I'm trying to make a secure session. So I'm following along in this tutorial to make an entire secure session PHP class. http://tutorial-resource.com/2011/10/a-secure-session-management-class-in-php/ I followed along in the tutorial, creating every piece line by line so I could understand it as best I could, but there are a few things I don't understand. First, I want to add certain lines so they stay page to page, such as "first name", "last name", and a customer id number. Then there's the line near the end of the tutorial which really confuses me: # Update the name. $sessionInfo->sessionData['fullname'] = "My New name"; $sessionInfo->setSessionData(); If SessionData is a TEXT data field in the MySQL database, and we are setting sessionData['fullname'] as some string, how is that accessed? May I arbitrarily make more fields such as sessionData['first_name'], sessionData['last_name'] and sessionData['customer_id']? How would that work if the sessionData row in the database is one row of text? Also, I see nothing in the class about what needs to happen when a user clicks Logout. Lets say I need to make my own Logout button, which is fine. How would properly terminating a session work?

Link to comment
Share on other sites

If SessionData is a TEXT data field in the MySQL database, and we are setting sessionData['fullname'] as some string, how is that accessed?
That field is a text field which stores the text that represents a serialized array. When the data is read from the database, the string is unserialized back into an array structure. When you're using it, it's an array. So yes, you can set any additional elements you want in the array and they will also get serialized and unserialized with the rest of the array. For logging out, the easiest way would probably be to add a method to the session class to delete the session row from the database.
Link to comment
Share on other sites

That field is a text field which stores the text that represents a serialized array. When the data is read from the database, the string is unserialized back into an array structure. When you're using it, it's an array. So yes, you can set any additional elements you want in the array and they will also get serialized and unserialized with the rest of the array. For logging out, the easiest way would probably be to add a method to the session class to delete the session row from the database.
That's amazing. My coworker and I were like "oh, TEXT is some string", when in fact it has very useful properties that make it different from say, a VARCHAR. That's fantastic. Thanks!
Link to comment
Share on other sites

The major difference between text and varchar is the size. While a varchar can technically be a length of 65535, that length is also the maximum size for a single row, so a varchar column of that size would be the only column in the row (it would have to be less than that depending on the character set used and overhead). A text column (longtext) can contain up to 4GB of text, which doesn't apply to the maximum row size because the contents of the column are stored separately from the row. Only the length of the data is stored with the row data. Other than that, varchar and text are pretty similar, they can both contain the same type of data and indexes.

Link to comment
Share on other sites

Another this missing from this article is the way to check a username and password. I will be checking for an email and password actually, and while I know how to do that it says there is a way to make the session class check it. If I make a registration page (which I have) and I am filling a table called "online_customers" with registration entries, what needs to happen in the sessions table?

Link to comment
Share on other sites

You don't need to change the session table to add features to your application, it only stores whatever arbitrary session data you want as a serialized array, and some metadata about the session. if you wanted a login method then you would add a method to the session class where you would pass the email address and password and the method would authenticate them against whatever table in your database keeps that information, then it would start a session for them and populate whatever session data you want them to have.

Link to comment
Share on other sites

I'm trying a quick and dirty login without a registration that checks for an existing customer in the database, and I have wierd errors. The end of my login, after successfully finding a user looks like this.: while($row=mysql_fetch_array($result)){//Grab the database pieces the customer will need throughout the page.$customer_id = $row['customer_id'];$first_name = $row['first_name'];$last_name = $row['last_name'];$first_name = $row['price_level'];} //Give the user a session.$sessions = new sessionsClass;$sessions->_sessionStart(); $sessionInfo = $sessions->sessionCheck(); if( $sessionInfo = false ){ # This session is invalid. Tell the user.}else{ # Update the name. $sessionInfo->sessionData['customer_id'] = $customer_id; $sessionInfo->sessionData['first_name'] = $first_name; $sessionInfo->sessionData['last_name'] = $last_name; $sessionInfo->sessionData['price_level'] = $price_level; $sessionInfo->setSessionData();//this is line 71 # Session is valid, can use the data. echo "Your name is ".$sessionInfo->sessionData['first_name']." ".$sessionInfo->sessionData['last_name']."<br>";} And the error log on my hosting service says this:PHP Fatal error: Call to undefined method stdClass::setSessionData() in /home4/danielga/public_html/templogin/logincheck.php on line 71 setSessionData clearly exists. It looks like this, straight out of the tutorial page, modified so it uses my database: public function setSessionData(){$dbuser = "(*@#&^$%(&*@#^%";$dbpass = "@#*$%&^@#(&*%^@#";$host = "@#&*(%^)(@^%t";$dbname = "@#I%@#(%*^#@)"; // database connection mysql_connect("localhost", $dbuser, $dbpass) or die(mysql_error());mysql_select_db($dbname) or die("Unable to select database");//Encrypt the data. $serialiseData = serialize( $this->sessionData ); //Update the session data. mysql_query( "UPDATE sessions SET sessionData = '{$serialiseData}' WHERE sessionHash = '{$this->sessionHash}'" ); } What could be the problem here?Also, as I implement this I still wonder, if I'm making a shopping cart, what needs to be included page to page to page so that generic users can still see the typical things I've set up whereas logged in users use the personalized session? That's my ultimate goal because the shopping cart I'm working on at my internship has a major goal of giving logged in customers personalized prices, hence the "$sessionInfo->sessionData['price_level'] = $price_level;" line.

Edited by Dakkadakka
Link to comment
Share on other sites

Should you be using $sessionInfo in that code or $sessions?

Also, as I implement this I still wonder, if I'm making a shopping cart, what needs to be included page to page to page so that generic users can still see the typical things I've set up whereas logged in users use the personalized session?
It would be like a normal PHP session, on each page you would check for an active session and personalize the page if you find one.
Link to comment
Share on other sites

I don't know. SessionInfo looks like this according to the tutorial. Thank you for the details on evaluating sessions. <?php include "class.session.php"; $sessions = new sessionClass; $sessions->_sessionStart(); $sessionInfo = $sessions->sessionCheck(); if( $sessionInfo = false ){ # This session is invalid. Tell the user.}else{ # Session is valid, can use the data.} ?>

Link to comment
Share on other sites

That's wierd. Maybe it's wrong in the tutorial. Changing SessionInfo to sessions when using the sessionData and setSessionData lines removed the error in the error long. Still, it echos back a blank line: echo "Your name is ".$session->sessionData['first_name']." ".$session->sessionData['last_name']."<br>(and the peronalized Level is ".$session->sessionData['price_level'].")"; It turns out another problem problem might be in sessionCheck: public function sessionCheck(){$dbuser = "@#)(*%&@#)*(%";$dbpass = "&&********";$host = "localhost";$dbname = "*********"; // database connectionmysql_connect("localhost", $dbuser, $dbpass) or die(mysql_error());mysql_select_db($dbname) or die("Unable to select database");//Set the hash which we get from the class. $hash = mysql_real_escape_string( $this->sessionHash );//The IP address is grabbed directly from the server. $ip = $_SERVER['REMOTE_ADDR'];//The Expiration time is recalculated. $expiryLimit = time() - $this->expireTime; //Find the matching session in the database. $check = mysql_query( "SELECT * FROM sessions WHERE sessionHash = '{$hash}' AND sessionIP = '{$ip}'" ); if( mysql_num_rows( $check ) > 0 ) { # Session exists and is valid. We update the session.//First, we find that session all over again. $sessionInfo = mysql_fetch_array( $check );//We decode it.$this->sessionData = unserialize( $sessionInfo['sessionData'] );//We add an extra fifteen minutes.$expire = time() + $this->expireTime;//We update this session in the database.$update = mysql_query( "UPDATE sessions SET sessionExpire = '{$expire}' WHERE sessionHash = '{$hash}'" ); return true; } else { # Session does not exist or is invalid. return false; }} Serialization is something I've never worked with before, but the internet says that serialization changes an object to "stdClass" if the class cannot be found. But this function is IN the class itself. I can't make the class include itself: that causes a memory error. All my files include the class file at the top, how do I make sure it's acknowledging the class? Also, the session class has no insertion of any kind. What is the point of making the mysql db if it can't create new sessions? When does this need to happen? In the class or during login?

Edited by Dakkadakka
Link to comment
Share on other sites

Serialization is something I've never worked with before, but the internet says that serialization changes an object to "stdClass" if the class cannot be found. But this function is IN the class itself. I can't make the class include itself: that causes a memory error. All my files include the class file at the top, how do I make sure it's acknowledging the class?
This isn't serializing an object, it's serializing an array. The sessionData property of the class is the array that gets serialized and stored in the database.
Also, the session class has no insertion of any kind. What is the point of making the mysql db if it can't create new sessions? When does this need to happen? In the class or during login?
As far as I can tell that's right, it never inserts data. That's obviously a problem. Personally, I would automate this more. I would have the constructor run the sessionCheck method instead of you having to do that manually, and if that returned false then it would create a new session. Or, the sessionCheck method could create the new session in the database itself.
Link to comment
Share on other sites

Lovely. I just ordered a php security book to see if I can get something more competent. In the meantime, I still really like this tutorial and want to improve upon it. It's just too incomplete. So we have this tutorial and a database with many unused fields and we need to create a new session to be inserted into the database. So modifying the createCookie() function seems to be key.First here is the database:

  • sessionHash – varchar – length:32
  • sessionIP – varchar – length:30
  • sessionTime – int – length:11
  • sessionPage – varchar – length:100
  • sessionData – text
  • sessionExpire – int – length:11

I threw in an incremental index as well called sessionID. Now maybe we can fix createCookie. //Create a cookie.public function createCookie(){//This variable is a unique, encrypted user identifier. $hash = md5( time() . uniqid() );//This creates a cookie ON THE USER DEVICE. setcookie( $this->cookiename, $hash, time()+$this->expireTime ); $this->sessionHash = $hash;//Set the hash which we get from the class. $hash = mysql_real_escape_string( $this->sessionHash );//The IP address is grabbed directly from the server. $ip = $_SERVER['REMOTE_ADDR'];//An expiration time is calculated to be 15 minutes + the user's time of login.$expire = time() + $this->expireTime;$dbuser = "(&*%(*&%^&*(";$dbpass = "(&*%^^&%^&";$host = "&^%*^&%^&%";$dbname = "43082976258496"; // database connectionmysql_connect("localhost", $dbuser, $dbpass) or die(mysql_error());mysql_select_db($dbname) or die("Unable to select database");$sql = sprintf("INSERT INTO `sessions` (sessionHash, sessionIP, sessionData, sessionExpire) VALUES ($this->sessionHash, $ip, this->sessionData, this->ExpireTime)" );mysql_query($sql);}Would something like this work? There is still the matter of the loggin using setSessionData, which only UPDATEs, and doesn't INSERT. So should I modify what happens during the login, or the SetSessionData function? How about I change it like this? //Give the user a session.$sessions = new sessionsClass;$sessionInfo = $sessions->sessionCheck(); if( $sessionInfo = false ){ # This session is invalid. Tell the user.}else{//I just changed this from $sessionInfo to $sessions to see what would happen # Update the name. $sessions->sessionData['customer_id'] = $customer_id;$sessions->sessionData['company_name'] = $company_name;$sessions->sessionData['first_name'] = $first_name;$sessions->sessionData['last_name'] = $last_name;$sessions->sessionData['price_level'] = $price_level;$sessions->_sessionStart(); //$sessions->setSessionData();# Session is valid, can use the data. echo "Your name is ".$session->sessionData['first_name']." ".$session->sessionData['last_name']."<br>(and the peronalized Level is ".$session->sessionData['price_level'].")";} Unfortunately that last echo prints out a bunch of blanks for each sessionData element at the moment.

Edited by Dakkadakka
Link to comment
Share on other sites

You only need to insert once, if it does that when the cookie gets created then that's enough. There are more issues with this though, like deleting expired sessions. The session class from that tutorial really only adds IP address checking and storing session data in the database. There's an easier way to do that, where all of the existing session functionality of PHP is used, except you can control where the session data gets stored. When the session gets started you can also add an additional IP address check and start a new session if necessary instead of using the old one that doesn't match the IP. There's a set of functions here that will remap the PHP session handling to use a database: http://w3schools.invisionzone.com/index.php?showtopic=9731

Link to comment
Share on other sites

Thank you for the link. I think I'll just switch to that since I'm quite pressed for time. How do I rewrite the conclusion of my login to create the session to this version? I have these four fields that I would like to go in what is now the "content" column of the new database. //-create while loop and loop through result setif ($found == 0){echo 'Wrong customer number';}else{while($row=mysql_fetch_array($result)){//Grab the database pieces the customer will need throughout the page.$customer_id = $row['customer_id'];$first_name = $row['first_name'];$last_name = $row['last_name'];$price_level = $row['price_level'];} //Give the user a session HERE }?> And then what can I add to the beginning of other pages to establish a flag for a logged in user from a non-logged in user?

Edited by Dakkadakka
Link to comment
Share on other sites

You just use sessions normally. That code already includes a call to session_start at the end of it, so if you're using that code as-is then that's already done. Otherwise, like with any other PHP session you just use session_start to start or continue a session, and the $_SESSION array to store your data.

Link to comment
Share on other sites

EDIT - wait... I JUST got the session working! I can see it echoing back whats in the $_SESSION array! It's beaaaauuuutiful. I guess my last question is, what do I need to put in a customer logout.php to properly make sure the proper session destroy is called?

Edited by Dakkadakka
Link to comment
Share on other sites

I just modified a bunch of pages to check the $_SESSION array, and now it's time to test my sessions. To get the login working I have commented out the debug lines and created a header() to move to the main page after a successful session creation, but it tells me [an error occurred while processing this directive.] What could be wrong? <?phpinclude 'session.php'; if(empty($_POST['customer_id'])) { echo("Customer ID is empty!"); } $customer_id = trim($_POST['customer_id']);$dbuser = "(*@)^()&*@#";$dbpass = "@#)&*%^#@(%";$host = "localhost";$dbname = "#^&@%($"; // database connection mysql_connect("localhost", $dbuser, $dbpass) or die(mysql_error());mysql_select_db($dbname) or die("Unable to select database"); //This query grabs all the puchases going back up to three months//This debug line displays the query$query = "SELECT * FROM customers WHERE customer_id = '$customer_id'";//echo $query."<br>"; $found = 0;$result = mysql_query($query) or die(mysql_error()); while($row = mysql_fetch_array($result)){//Grab the database pieces the customer will need throughout the page.//echo "Checking customer number ".$row['customer_id']."<br>";if ($row['customer_id'] == $customer_id){$found = 1;//Grab the database pieces the customer will need throughout the page.$customer_id = $row['customer_id'];$first_name = $row['first_name'];$last_name = $row['last_name'];$price_level = $row['price_level'];//These debug lines check if the database query was successful//echo 'Customer ID is '.$customer_id.'<br>';//echo 'First Name is '.$first_name.'<br>';//echo 'last name is '.$last_name.'<br>';//echo 'price level '.$price_level.'<br>';break;} } //-create while loop and loop through result setif ($found == 0){echo 'Wrong customer number';}//Give the user a session HERE$_SESSION['customer_id'] = $customer_id;$_SESSION['first_name'] = $first_name;$_SESSION['last_name'] = $last_name;$_SESSION['price_level'] = $price_level; //This debug line makes sure the session array has all areas filled//echo "Your name is ".$_SESSION['first_name']." ".$_SESSION['last_name']."<br>(and the peronalized Level is ".$_SESSION['price_level'].")";header("index-in.php"); ?>

Link to comment
Share on other sites

You can't send a header after sending output, so one of those echo statements may be sending output and then causing the header to fail.
That's what I thought at first, but the error_log would log such an error. And just in case, I commented every single echo, even the ones that are fail conditions for the login. Not only do I not get this error message in my browser, the error_log doesn't show anything. This is what it looks like now: <?phpinclude 'session.php'; /*if(empty($_POST['customer_id'])) { echo("Customer ID is empty!"); }*/ $customer_id = trim($_POST['customer_id']);$dbuser = "";$dbpass = "";$host = "localhost";$dbname = ""; // database connection mysql_connect("localhost", $dbuser, $dbpass) or die(mysql_error());mysql_select_db($dbname) or die("Unable to select database"); //This query grabs all the puchases going back up to three months//This debug line displays the query$query = "SELECT * FROM customers WHERE customer_id = '$customer_id'";//echo $query."<br>"; $found = 0;$result = mysql_query($query) or die(mysql_error()); while($row = mysql_fetch_array($result)){//Grab the database pieces the customer will need throughout the page.//echo "Checking customer number ".$row['customer_id']."<br>";if ($row['customer_id'] == $customer_id){$found = 1;//Grab the database pieces the customer will need throughout the page.$customer_id = $row['customer_id'];$first_name = $row['first_name'];$last_name = $row['last_name'];$price_level = $row['price_level'];//These debug lines check if the database query was successful//echo 'Customer ID is '.$customer_id.'<br>';//echo 'First Name is '.$first_name.'<br>';//echo 'last name is '.$last_name.'<br>';//echo 'price level '.$price_level.'<br>';break;} } //-create while loop and loop through result set/*if ($found == 0){echo 'Wrong customer number';}*///Give the user a session HERE$_SESSION['customer_id'] = $customer_id;$_SESSION['first_name'] = $first_name;$_SESSION['last_name'] = $last_name;$_SESSION['price_level'] = $price_level; //This debug line makes sure the session array has all areas filled//echo "Your name is ".$_SESSION['first_name']." ".$_SESSION['last_name']."<br>(and the peronalized Level is ".$_SESSION['price_level'].")";header("index-in.php"); ?> Edited by Dakkadakka
Link to comment
Share on other sites

Guest So Called
header("index-in.php");
That does not look valid to me. Or is it just a debug statement? Edited by So Called
Link to comment
Share on other sites

That does not look valid to me. Or is it just a debug statement?
Changing the header to index.html did not to the trick. I got the same error. You bring up a good point: index.html is the home page, and index-in.php is my script to go along with it. I've been so used to working with index-in.php that I wrote it as if it's the main page. It's a shame changing the header didn't do the trick. Edited by Dakkadakka
Link to comment
Share on other sites

Guest So Called

I don't know of any valid header that contains only a file name. Why would any browser know what that means? Perhaps you meant something like this:

header('Location: index.html');

Headers usually consist of a header type and a value.

Link to comment
Share on other sites

You can't send a header after sending output, so one of those echo statements may be sending output and then causing the header to fail.
Ah, that was silly of me. This brings me to the index page properly, I also modified the successful login to implement the session: //Give the user a session HEREsession_start();$_SESSION['customer_id'] = $customer_id;$_SESSION['first_name'] = $first_name;$_SESSION['last_name'] = $last_name;$_SESSION['price_level'] = $price_level; //This debug line makes sure the session array has all areas filled//echo "Your name is ".$_SESSION['first_name']." ".$_SESSION['last_name']."<br>(and the peronalized Level is ".$_SESSION['price_level'].")";header('Location: index.html'); ?> But the index page itself immediately checks this and fails. Am I using sessions properly here? At the beginning I check if the session customer id is set. I threw in some debug statements and they always say it's not a logged in person. <?phpinclude 'session.php';if (isset($_SESSION['customer_id'])){ //Grab their session dataecho 'We have a customer id<br>';$customer_id = $_SESSION["customer_id"];$first_name = $_SESSION["first_name"];$last_name = $_SESSION["last_name"];$price_level = $_SESSION["price_level"];echo 'Customer id is '.$customer_id.'<br>';echo 'first_name is '.$first_name.'<br>';echo 'last_name is '.$last_name.'<br>';echo 'Price Level (NEVER SHOW THEM THIS) '.$price_level.'<br>';}else{echo 'Not a logged in person<br>';} ?>
Link to comment
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
×
×
  • Create New...