What Does It Mean to Login?
HTTP is a stateless protocol. This means that every request to the web server is treated as if no request came before it. Consequently this changes how a web application handles logging in/out versus how, for example, an operating system handles it. In a web application we're limited to basically just mimicking the state of being "logged in." We do this through the use of identifiers such as cookies and sessions.
Due to HTTP's limitations and how we're forced to handle logging in, every single page load much check for the existence of these identifiers, validate them, and then decide if the user is in fact logged in or not. And that's what we're going to do in this tutorial: write a class that validates the given username/password combination, set the login identifiers, and then provide utility to both validate the identifiers and determine if the user is logged in or not. Lets get started.
Application Flow and Necessary Information
This class will get used in one of two different ways and the flow depends entirely on how it's called.
Check Login
- Check for existence of _user and _rand (password) cookies.
- Make sure that the _rand (password) cookie is what it should be (crypt hash of the encrypted password with our pass phrase as the salt).
- If all checks out, we're logged in.
Attempt to log the user in
- Make sure we're not already logged in.
- Authenticate the submitted username and password combination.
- If all checks out then create our cookies.
Password Cookie (cookieName_rand)
This cookie is the output of PHP's crypt () function, using the encrypted password from the database as the string and our defined pass phrase as the salt.
Registering Users
Sadly many people do not learn of the other encryption methods PHP supports until too late. Instead, most go a long time using MD5 as their encryption method, without the use of a salt even. As such you'll need to know that in order for this class to work properly all passwords in your database must be created using the PHP crypt () function. As an example of how you might insert a username/password combo that conforms to the standards set by this class:
<?php
$conn = mysql_connect ("localhost", "username", "password")
or die (mysql_error ());
mysql_select_db ("database", $conn)
or die (mysql_error ());
$username = "bobby";
$password = crypt ("ohitsapassword");
mysql_query ("INSERT INTO login (username, password) VALUES ('$username', '$password')");
?>Defining Our Class
abstract class Login_Abstract
{
public function __get ($key)
{
return $this -> $key;
}
public function __set ($key, $val)
{
$this -> $key = $val;
}
public function sanitize ($val)
{
return strip_tags (htmlspecialchars ($val));
}
}
class Login extends Login_Abstract
{
}If you're not familiar with class abstraction or OOP in general: what we've done is defined an abstract class for our main Login class. An abstract class defines any methods that all child-classes must implement. It's also a useful place to put generic code that isn't specific to our actual model. And that's what we're using it for here.
The __ get () and __ set () are "magic methods." In short, they're methods already defined by PHP that you can use to make your life easier. These two are used to, unsurprisingly, get a class property (variable) or set one. This lets us avoid having to set up specific getters and setters for any properties we may use in the Login class. Less code = less worries.
The last bit that needs to be mentioned is the sanitze () method. This performs rudimentary sanitizing (I know, I know... coming out of left field with that one) on the given string. We'll use this to clean out any HTML before passing it off to the database.
Enter: Properties
We've declared our Login class as an extension of the Login_Abstract abstract class in the previous section. Lets populate it with the necessary properties to make the magic happen.
class Login extends Login_Abstract
{
private $cookieName = "our_login";
private $passPhrase = "gotcha";
private $dbServer = "localhost";
private $dbUsername = "username";
private $dbPassword = "password";
private $dbDatabase = "our_table";
private $dbUserTable = "users";
protected $db;
static private $username;
static private $password;
}There's a decent amount of information needing to be stored here. The first two sets of properties are all to be edited in order to make the script work. cookieName is what we'll be calling our cookies and passPhrase is an application-specific string we're appending to the password when storing it in a cookie. This works as a basic additional salt, simply encapsulating the most bare end of our script a little further. You'll see this used and explained later, but just know that passPhrase should be unique to every application this class gets used for.
The next block are all MySQL database details, it should be self-explanatory.
Lastly we have three properties that deserve a bit of describing. The db property is simply our MySQL resource identifier. And username / password are, unsurprisingly, the current user's username and password (encrypted, that is). We've declared these two as static properties so that they don't get reset should you create an instance of the Login object more than once during a request (as they shouldn't be changing).
And the bulk of the application
Still working inside of our Login class, lets build our constructor (it gets called every time a new instance of the Login object gets created).
public function __construct ()
{
$this -> db = mysql_connect ($this -> dbServer, $this -> dbUsername, $this -> dbPassword)
or die (mysql_error ());
mysql_select_db ($this -> dbDatabase, $this -> db)
or die (mysql_error ());
}As you can see this is pretty simplistic. Our Login class doesn't take any parameters so all the constructor has to do is connect to MySQL. Notice how we're setting mysql_connect () to our db property. This function is pretty boring so lets keep moving along.
We need a function that checks to see if our user/pass cookies exist and then to make sure they're valid. Lets look at how we do that:
public function check_status ()
{
if (isset ($_COOKIE["{$this -> cookieName}_rand"]) && isset ($_COOKIE["{$this -> cookieName}_user"]))
{
$username = $_COOKIE["{$this -> cookieName}_user"];
if ($_COOKIE["{$this -> cookieName}_rand"] == crypt ($this -> grab_password ($username), $this -> passPhrase))
{
// Cookie exists and it appears valid!
return true;
}
// Tampered cookies, delete them.
setcookie ("{$this -> cookieName}_user", "", time () - 3600);
setcookie ("{$this -> cookieName}_rand", "", time () - 3600);
}
// No cookie, thus definitely not logged in.
return false;
}You'll notice that the first thing we're doing is checking for the existence of our two cookies. If they don't exist then we know right away the viewer isn't logged in. If you're unfamiliar with the syntax you can encase PHP code inside curly brackets within a string; doing so will make the code inside the curly brackets get parsed. Thus "{$this -> cookieName}_rand" is the same as $this -> cookieName . "_rand. It's just a matter of personal preference. Feel free to format strings however you please.
Suggesting the cookies are set we then create a variable: $username. This is simply just the contents of the username cookie and we set this variable solely to be used in the next line.
Take a look at that now, but ignore the grab_password () method- it'll be explained later, just know that it grabs the encrypted password from the database. We're checking the contents of the password cookie to see if it matches what we want it to be. As we discussed at the beginning of the tutorial, our password cookie is a crypt hash of the encrypted password from the database and our unique pass phrase as a permanent salt. If the contents don't match up then something is wrong and we need to delete the two cookies and return false.
If, however, everything matches up then we're hereby satisfied and we return true: we're logged in!
So great, we have ourselves a method that checks to see if we're currently logged in. That's nice but we still need a way to actually make the user log in! Lets do that now.
public function attempt_login ($username, $password)
{
// Ensure that we're not already logged in
if (!isset ($_COOKIE["{$this -> cookieName}_user"]) && !isset ($_COOKIE["{$this -> cookieName}_rand"]))
{
if ($this -> confirm_password ($username, $password))
{
setcookie ("{$this -> cookieName}_user", $username);
setcookie ("{$this -> cookieName}_rand", crypt ($this -> grab_password ($username), $this -> passPhrase));
}
// Authentication failed
return false;
}
// Either the confirmation was valid or the user is already logged in
return true;
}
private function confirm_password ($username, $password)
{
$tmpPassword = $this -> grab_password ($username);
$usrPassword = crypt ($password, $tmpPassword);
if ($tmpPassword == $usrPassword)
return true;
return false;
}I'm giving you two methods here because both are pretty straight forward. As we discussed earlier the steps to logging in are: 1- check username/password combination, 2- create required identifiers (cookies in our case) to represent the success. And that's exactly what we do here.
Right off the bat we just make sure we're not already logged in (by checking for our cookies). Then we call the confirm_password () method, giving it the username and password we were given. We'll ignore that method for right now. If the login details validate then we create our cookies. That's step two of two and we've officially logged a user in!
But lets take a closer look at how we confirm the passwords.
First off we call the grab_password () method again. Ignore if for me, it'll be explained next- just remember that it returns the encrypted password from the database. We then create a new password using PHP's crypt () function, just like we saw before with the cookie validation. The first parameter is the given password but look at the second parameter: it's the password that is stored in the database. By passing this as the salt the crypt () function will actually grab the unique and random salt used when creating the password and use it instead. As such, if the given password was correct then our new password will equal out to be the same as the password stored in the database. Which we check for. If that's the case, voila: passwords validate and we return true.
Now lets finish this class off with the grab_password () method we've seen used a number of times now!
private function grab_password ($username = "")
{
// Check to see if we need to fill out the class username/password properties
// or if it looks like the given username appears to have been tampered with
if (empty ($this -> password) || empty ($this -> username) || $username != $this -> username)
{
$query = mysql_query (
sprintf ("SELECT password FROM {$this -> dbUserTable} WHERE username = '%s'",
mysql_real_escape_string ($this -> sanitize ($username))
),
$this -> db
);
if (mysql_num_rows ($query) == 1)
{
$this -> username = $username;
$this -> password = mysql_result ($query, 0);
return mysql_result ($query, 0);
}
return null;
}
return $this -> password;
}The primary job of this method is to return the current user's encrypted password.
The first if statement does three very distinct checks. First is looks to see if the password property of the class is empty. Then it looks to see if the username property is empty. And last, it looks to see if the username given as a parameter is different than the username stored in the class property.
The first two checks should seem self-explanatory- look to see if they exist because if they do we can ignore querying the server and simply shoot back the value of the class property instead. But the last check might seem a bit out of place. But it's quite simple: it's just an additional line of validation. The username shouldn't change on us while the password remains the same. At least not without properly logging out. So if, for whatever reason, the attempted username doesn't match up with the stored username, we query.
The query is straight forward. All we want from the table is the password field belonging to the given username. I use the sprintf () function to format my query (if you're unfamiliar with it please click the link and read PHP.net's explanation- it's a great function to utilize). Notice though that we're passing the given username through our sanitize () method and then mysql_real_escape_string (). This effectively clears us of possible SQL injections, something you'll want to do anytime you're passing third party data into your database.
Anyways, we check the results of that query and see if it returned one. If so then we have a success. So we set the class username and password properties to what they need to be (password = the password we just queried from the database) and we also return the password.
And that is that, a complete class capable of logging a user in securely. Here's the class in its entirety.
<?php
abstract class Login_Abstract
{
public function __get ($key)
{
return $this -> $key;
}
public function __set ($key, $val)
{
$this -> $key = $val;
}
public function sanitize ($val)
{
return strip_tags (htmlspecialchars ($val));
}
}
class Login extends Login_Abstract
{
private $cookieName = "our_login";
private $passPhrase = "gotcha";
private $dbServer = "localhost";
private $dbUsername = "root";
private $dbPassword = "";
private $dbDatabase = "test";
private $dbUserTable = "login";
protected $db;
static private $username;
static private $password;
/**
* Connect to the database, initiating our MySQL resource ID.
*
* @access public
* @return void
*/
public function __construct ()
{
$this -> db = mysql_connect ($this -> dbServer, $this -> dbUsername, $this -> dbPassword)
or die (mysql_error ());
mysql_select_db ($this -> dbDatabase, $this -> db)
or die (mysql_error ());
}
/**
* Validates whether the user is logged in or not. check_status () is also
* responsible for validating the integrity of the login identifiers.
*
* @access public
* @return bool True if the user is logged in
* @return bool False if the user isn't logged in or there was a mishap
*/
public function check_status ()
{
if (isset ($_COOKIE["{$this -> cookieName}_rand"]) && isset ($_COOKIE["{$this -> cookieName}_user"]))
{
$username = $_COOKIE["{$this -> cookieName}_user"];
if ($_COOKIE["{$this -> cookieName}_rand"] == crypt ($this -> grab_password ($username), $this -> passPhrase))
{
// Cookie exists and it appears valid!
return true;
}
// Tampered cookies, delete them.
setcookie ("{$this -> cookieName}_user", "", time () - 3600);
setcookie ("{$this -> cookieName}_rand", "", time () - 3600);
}
// No cookie, thus definitely not logged in.
return false;
}
/**
* Physically log the user in, checking their username and password against
* the database.
*
* @access public
* @param string Username
* @param string Password
* @return bool True if the authentication checked out okay
* @return bool False if it didn't
*/
public function attempt_login ($username, $password)
{
// Ensure that we're not already logged in
if (!isset ($_COOKIE["{$this -> cookieName}_user"]) && !isset ($_COOKIE["{$this -> cookieName}_rand"]))
{
if ($this -> confirm_password ($username, $password))
{
setcookie ("{$this -> cookieName}_user", $username);
setcookie ("{$this -> cookieName}_rand", crypt ($this -> grab_password ($username), $this -> passPhrase));
}
// Authentication failed
return false;
}
// Either the confirmation was valid or the user is already logged in
return true;
}
/**
* This method handles the logic behind checking the given password against
* the password stored in the database.
*
* @access private
* @param string Username
* @param string Password
* @return bool True, passwords are equal
* @return bool False, passwords are not the same
*/
private function confirm_password ($username, $password)
{
$tmpPassword = $this -> grab_password ($username);
$usrPassword = crypt ($password, $tmpPassword);
if ($tmpPassword == $usrPassword)
return true;
return false;
}
/**
* Query the database and snag the current encrypted password from it,
* returning it to the caller and also setting it to the class property for
* future use.
*
* @access private
* @param string Username
* @return string The encrypted password (from the database)
* @return null Invalid username, nothing to retrieve
* @return string The encrypted password (from the class property)
*/
private function grab_password ($username = "")
{
// Check to see if we need to fill out the class username/password properties
// or if it looks like the given username appears to have been tampered with
if (empty ($this -> password) || empty ($this -> username) || $username != $this -> username)
{
$query = mysql_query (
sprintf ("SELECT password FROM {$this -> dbUserTable} WHERE username = '%s'",
mysql_real_escape_string ($this -> sanitize ($username))
),
$this -> db
);
if (mysql_num_rows ($query) == 1)
{
$this -> username = $username;
$this -> password = mysql_result ($query, 0);
return mysql_result ($query, 0);
}
return null;
}
return $this -> password;
}
}
?>And an example of how you might use it:
<?php
/**
* Create an instance of our Login object
*/
$login = new Login ();
/**
* You would pass the username/password given in the form through
* this method, but I don't feel like making a form so here's some pretend.
*/
if ($login -> attempt_login ("bobby", "pass"))
{
// Oh yay, their data checked out
}
/**
* And this is what you'd call, page-to-page, to see if the viewer is logged
* in or not. Example, you might assign it to a variable at the beginning
* of the page load, and then just use that variable to determine what the
* viewer can see or cannot see.
*/
if ($login -> check_status ())
{
// They are in fact logged in
}
/**
* Example of setting it to a variable
*/
$is_logged = $login -> check_status ();
if ($is_logged)
{
// Logged in!
}
?>

Help


Actions
Bookmark
Tutorial info
View Members Tutorials







RSS Feed