Webforumz Newsletter - December 2007

Tutorials

Login/Registration With PHP Sessions

Introdution

In this tutorial we will be learning how to create a login / registration system for a website using PHP and MySQL. You will need this ZIP file which contains all the files necessary for this tutorial.

The User Database

First we need to create the MySQL database that will contain our user's login info. Only two fields are really necessary - username and password - but we will also require the users to supply an email so we will have three fields in our database:


CREATE TABLE `users` (
	`username` varchar(255) ,
	`password` varchar(255) ,
	`email` varchar(255) ,
	PRIMARY KEY (`username`)
);

All three fields are VARCHAR with a maximum length of 255 characters. The username field will be our primary key which forces user names to be unique. We will be performing a manual check on new user names but even if we didn't, the database would throw an error if someone would try to register with a user name that already exists.

The PHP

The code in the ZIP file is well-commented, but we will take a look at the key points in each file.

index.php

session_start();

This is the key to our system - PHP sessions. A session allows us to temporarily store information on the server. Each session has a unique ID which is stored in a cookie or appended to the URL as a query string. The session_start() function creates a new session or resumes the current session based on its ID. Session variables can be read and written like so:



session_start();
$_SESSION['bla'] = 'blub';
echo($_SESSION['bla']);

The next point of interest in our code is here:


if(isset($_SESSION['suo'])) {
	$user=unserialize($_SESSION['suo']);
} else {
	// new user object
	$user=new user();
}

Here we check to see if a user object already exists in the current session. If yes, then we restore the object using the unserialize() function. If not, then we create a new, default user object.

Let's take a little time to check out user.php and explain the user class.

user.php

This document contains the definition of our user class.

user()

In the constructor,:


function user($user_name='guest') { // constructor
	$this->username=$user_name;
	$this->logged_in=false;
}

the class variables ($username, $logged_in) are initialized for a guest. We will then use the member functions to check if the user name exists, attempt to log the user in and validate user input.

userExists()


function userExists() { // check if the user exists
	// query the database
	$query=mysql_query("SELECT * FROM users WHERE username =
'".mysql_escape_string($this->username)."'"); if($row=mysql_fetch_object($query)) { return true; } else { $this->last_error='Username does not exist!'; return false; } }

This function queries the database to see if the current $username exists. If yes, it returns true; if not, then it sets the $last_error message and returns false.

login()


function login() { // attempt to login user
	if($this->userExists()) { // check if user exists
		// check if password was sent via JS hash
		if($_POST['login_pass_md5']!='') {
			$password=$_POST['login_pass_md5'];
		} else { // catch users that have JS turned off
			$password=md5($_POST['login_password']);
		}
		// query the database
		$query=mysql_query("SELECT * FROM users 
WHERE username = '".mysql_escape_string
($this->username)."' AND password
= '".mysql_escape_string($password)."'"); if($row=mysql_fetch_object($query)) { // log the user in $this->logged_in=true; return true; } else { $this->last_error='Password incorrect!'; return false; } } return false; }

This is where we attempt to log the user in.

First check if the user exists by calling the userExists() member function:


if($this->userExists()) {

Then query the database to see if there is a user with the provided password:



$query=mysql_query("SELECT * FROM users WHERE username = '".
mysql_escape_string($this->username)."' AND password =
'".mysql_escape_string($password)."'");

If yes, set the $logged_in variable to true and return true; if not, set the $last_error message and return false.

validateEmail()

This function uses preg_match() to check if the provided string is a valid email address.

The HTML

Let's put the PHP on a back burner and check out the HTML.

index.php

We have a normal XHTML document, with one special feature - the content of the wrapper div is decided by this line of PHP which checks if the $logged_in variable of the $user object is set to true:


if($user->logged_in) {

If the user is logged in then the page displays a welcome message. If not, the login / registration form is shown.

The Javascript

Let's put the HTML next to the PHP - on the back burner - and take a look at the Javascript. It is important to remember that the system still has to work if a user has none or disabled Javascript. JS can raise Usability but it can also quickly lower Accesibility....

index.php

Actually all we do on this page as far as Javascript is concerned is include md5.js and functions.js and then call functions defined in these files.

md5.js

MD5()

This function returns a md5 hash of the passed string. How it works in unimportant. That it works is enough.

functions.js

$()



function $(id) { // short hand for getElementById
	return document.getElementById(id);
}

This function is just a short hand version of the getElementById() function. Just makes life easier and increases the code readability....

init()

This is a good example of how to make sure our page works without JS. When the page loads, the register div is visible and the registerlink div is not visible. So, if JS is disabled, the user sees both the login and register divs and can still login or register. BUT, if JS is available, this function is called and the register div is blended out, the registerlink div is blended in and the user can then toggle between login and register.

toggleRegisterForm()

This function toggles the display of the login / registration divs.

hashPass(es)()

Because information sent via post is sent as text, it is not a clever idea to just post a user name and password. A much safer method is to hash the password client-side and then send it so that anyone who intercepts the post request will have to spend a few days attempting to crack the password with brute force before he can attempt to illegally use the user account. BUT, remember, our application also has to be accessible so when there's no JS the values in the form are posted normally (as plain text) and on the server-side (PHP) we'll have to remember to catch and handle this.

And Back To the PHP

Grab the PHP off the back burner, put the JS in its place and keep letting the HTML simmer.

index.php cont.

Let's look at the section where we handle the user input. First we check if the user posted anything:


if(count($_POST)>0) {

Then we check if the user is trying to login or register by checking the value of the user name variable:


if($_POST['login_name']!='') {
	// code...
} else if($_POST['register_name']!='') {
	// more code...
}

Logging the User In

If the user is trying to log in (the $_POST['login_name'] variable contains a value), then we first set the $user->username variable to the posted value and then call the $user->login() member function to try and log the user in. If the function returns false (the user could not be logged in), then we add the $user->last_error message to the $error_message array. If the function returns true, then we know the user was successfully logged in and can now access the password protected content.

Registering a New User

The registration process is a bit more complicated. The first step is to validate the users input. Any errors are added to the $error_messages array. The first check is if a user with the supplied name already exists in the database. First we have to set the $user->username variable and then check return value of the $user->userExists() function. After that we check if the two passwords are the same: first check if the passwords were posted as JS md5's from client-side and, if not, hash them server-side. Then we compare them to make sure they match. The last validation step is to check if the supplied email address has the right format. This is taken care of by the $user->validateEmail() member function and all we have to do is check the return value.

If all the checks pass:


if(count($error_messages)==0) {

then we can INSERT the new user in our database and log him in:


if(mysql_query("INSERT INTO users VALUES
		'".mysql_escape_string($user->username)."',
		'".mysql_escape_string($password1)."',
		'".mysql_escape_string($_POST['register_email'])."')")) {

and of course we remember to check if the login was successful.


if(!$user->login()) {
	$error_messages[]=array($user->last_error);
}

Outputting Errors


foreach($error_messages as $error_message) {
	echo('<p class="error">'.$error_message.'</p>');
}

In this foreach loop we iterate through all of the error messages in the $error_message array and output them to the user.

Finishing Off

At the very end of the page we store the $user object in a session variable so that it is available when the user moves on to another page:


$_SESSION['suo']=serialize($user);

And finally, close the database connection:


mysql_close($connection);

More Pages

another_page.php

This page is an example of another page with password protected content.

At the very top, we resume the session with session_start(). Then include the database connection and the user class definition. After that we initialize a new user object - either from the value stored in the session or we create a new user. Then we can check the $user->logged_in and, hey, presto, password protected content!

Still Coming...

Hopefully in one of the next versions of the Newsletter I'll be posting another tutorial in which we will learn how to pimp our login / registration system with some AJAX.

So, don't miss that! Now, take the HTML off the back burner, mix it together with the PHP, add a pinch of Javascript, spice to taste with CSS and enjoy. Good luck with your coding!!!