Reputation: 9900
I'm building a simple cms to help expand my knowledge of PHP, but unfortunately my cookies don't appear to be working. The file that I believe is the core of the problem is included below:
<?php
// First we execute our common code to connection to the database and start the session
require("common.php");
// Detect if the user has a cookie available. If a cookie is availbale, and a valid
// session is found then redirect the user to the appropriate QUEST landing page
if(isset($_COOKIE["qcore"]))
{
$query = "
SELECT TOP 1
u.*
FROM dbo.[User] AS u
INNER JOIN dbo.UserSession AS us
ON us.UserId = u.UserId
WHERE us.SessionId = :sessiontoken";
// The parameter values
$query_params = array(
':sessiontoken' => $_COOKIE["qcore"]
);
try
{
// Execute the query against the database
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
// Retrieve the user data from the database. If $row is false, then the session has
// likely expired and the user will be presented with the login form again.
$row = $stmt->fetch();
// This statement checks if data was available when retreiving user information
// using our session token.
if($row)
{
// Here I am preparing to store the $row array into the $_SESSION by
// removing the salt and password values from it. Although $_SESSION is
// stored on the server-side, there is no reason to store sensitive values
// in it unless you have to. Thus, it is best practice to remove these
// sensitive values first.
unset($row['Salt']);
unset($row['Password']);
// This stores the user's data into the session at the index 'user'.
// We will check this index on the private members-only page to determine whether
// or not the user is logged in. We can also use it to retrieve
// the user's details.
$_SESSION['user'] = $row;
// Redirect the user to the private members-only page.
// This will need to be changed once we have the QUEST logic flow sorted out
// to be the landing quest page.
header("Location: private.php");
die("Redirecting to: private.php");
}
else
{
// If no data is found then the session has expired, or been terminated
// and we need to remove the cookie to present the login form to the user
// To do this we set the cookie expiration date to one hour ago
setcookie("qcore", "", time()-3600);
}
}
// This variable will be used to re-display the user's username to them in the
// login form if they fail to enter the correct password. It is initialized here
// to an empty value, which will be shown if the user has not submitted the form.
$submitted_username = '';
// This if statement checks to determine whether the login form has been submitted
// If it has, then the login code is run, otherwise the form is displayed
if(!empty($_POST))
{
// This query retreives the user's information from the database using
// their username. SELECT TOP 1 prevents people from being able to edit
// their HTTP POST to fetch the entire table.
$query = "
SELECT TOP 1
*
FROM dbo.[User]
WHERE
Username = :username
";
$query_params = array(
':username' => $_POST['username']
);
try
{
// Execute the query against the database
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
// This variable tells us whether the user has successfully logged in or not.
// We initialize it to false, assuming they have not.
// If we determine that they have entered the right details, then we switch it to true.
$login_ok = false;
// Retrieve the user data from the database. If $row is false, then the username
// they entered is not registered.
$row = $stmt->fetch();
if($row)
{
// Using the password submitted by the user and the salt stored in the database,
// we now check to see whether the passwords match by hashing the submitted password
// and comparing it to the hashed version already stored in the database.
$check_password = hash('sha256', $_POST['password'] . $row['Salt']);
for($round = 0; $round < 65536; $round++)
{
$check_password = hash('sha256', $check_password . $row['Salt']);
}
if($check_password === $row['Password'])
{
// If they do, then we flip this to true
$login_ok = true;
}
}
// If the user logged in successfully, then we send them to the private members-only page
// Otherwise, we display a login failed message and show the login form again
if($login_ok)
{
// Here I am preparing to store the $row array into the $_SESSION by
// removing the salt and password values from it. Although $_SESSION is
// stored on the server-side, there is no reason to store sensitive values
// in it unless you have to. Thus, it is best practice to remove these
// sensitive values first.
if(!empty($_POST))
{
unset($row['Salt']);
unset($row['Password']);
}
// This stores the user's data into the session at the index 'user'.
// We will check this index on the private members-only page to determine whether
// or not the user is logged in. We can also use it to retrieve
// the user's details.
$_SESSION['user'] = $row;
// Generate a session token which is used locally as a key between the users cookie
// and their UserID, this prevents the user from being able to edit their cookie
// to login as another user.
$sessiontoken = dechex(mt_rand(0, 2147483647)) . dechex(mt_rand(0, 2147483647));
// Save our cookie 'qcore' with the users session id
setcookie("qcore", $sessiontoken);
// Insert a new session ID record, or update if one already exists. One should never
// exist but this is added as a precaution in case a session has expired whilst the login
// form was being filled out (EXTREMELY unlikely), but hey, why not.
$query = "
DECLARE @userid AS INTEGER = :userid
DECLARE @sessionid AS varchar(500) = :sessionid
IF EXISTS ( SELECT TOP 1 *
FROM dbo.UserSession
WHERE UserId = @userid )
UPDATE dbo.UserSession
SET SessionId = @sessionid
WHERE UserId = @userid
ELSE
INSERT INTO dbo.UserSession (
UserId ,
SessionId
) VALUES (
@userid ,
@sessionid)";
$query_params = array(
':userid' => $row['UserId'],
':sessionid' => $sessiontoken
);
try
{
// Execute the query to insert a new user session or update
// an existing one
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
// die("Failed to run query: " . $ex->getMessage());
die("Failed to run query: " . $ex->getMessage());
}
// Redirect the user to the private members-only page.
// This will need to be changed once we have the QUEST logic flow sorted out
// to be the landing quest page.
header("Location: private.php");
die("Redirecting to: private.php");
}
else
{
// Tell the user they failed
print("Login Failed.");
// Show them their username again so all they have to do is enter a new
// password. The use of htmlentities prevents XSS attacks. You should
// always use htmlentities on user submitted values before displaying them
// to any users (including the user that submitted them). For more information:
// http://en.wikipedia.org/wiki/XSS_attack
$submitted_username = htmlentities($_POST['username'], ENT_QUOTES, 'UTF-8');
}
}
?>
<h1>Login</h1>
<form action="login.php" method="post">
Username:<br />
<input type="text" name="username" value="<?php echo $submitted_username; ?>" />
<br /><br />
Password:<br />
<input type="password" name="password" value="" />
<br /><br />
<input type="submit" value="Login" />
</form>
<a href="register.php">Register</a>
And I suspect that something simple is going wrong in this area, after retreiving the session, but I've no idea what could be the cause:
// This statement checks if data was available when retreiving user information
// using our session token.
if($row)
{
Upvotes: 1
Views: 152
Reputation: 27227
This is a known issue with IIS (Azure runs IIS I believe?)
Reference: http://support.microsoft.com/kb/q176113
When a CGI application sends a Set-Cookie header with "302 Object Moved" response and Location header, Internet Information Server (IIS) ignores the cookie header.
This behavior is in violation of the CGI specification, which states, "Any headers that are not server directives are sent directly back to the client. Currently, this specification defines three server directives..."
There's a workaround in the linked page, which looks like it's a little involved for this project. Sorry. I have no real experience with microsoft products, so the best I can recommend is to try a different stack.
Upvotes: 1