Pages

Banner 468

Wednesday 13 April 2011

Building a Web space Management System 1

0 comments
 
Hello again. As I explained way back in my very first post I am a Middlesex University student studying for a degree in Internet Application Development. The purpose of this blog has been to share my thoughts and experiences on the practical work I've been carrying out during this course.

During my next few posts, I will be building a small Web space management System using PHP.

Task Definition

The aim of this task is to 'write a Web space management system suitable for handling Web site content of various types. It should allow users to add and organise files to a personal Web area which is secured by a password.'  The 'program' should support the following features:
  • Passwords are to be stored on a MySQL database
  • Authenticated users should be able to craete/upload/delete files and directories
  • Users should only be able to access their own space
  • Space quotas (limits)
We have our requirements, so let's get started!

1. The Login Form

The first step is to create a login form for our users. Later on in the series (when tackling MySQL connections) I will create a proper user registration form, but at this stage I will assume that I already have some registered users.  The login form accepts a username and a password (obviously) and will support a "remember me" feature. I've used HTML & CSS techniques discussed in my earlier posts to style the form. 

Here's what it looks like:


I've also added some simple javascript validations to prevent the form from being submitted if either the username or the password is left blank. However, it is always a good idea to duplicate such validations on the server as well since we have no way of knowing whether javascript is enabled on the client's browser!
I want to place these server-side validations on the login page itself so I have set the 'action' attribute of the login form to request itself. I don’t want to hard-code the name of the page so I will be using PHP to set the value of the “action” attribute of our form. Here’s one way of doing this:

<form action="<?php echo $_SERVER['SCRIPT_FILENAME'] ?>" method="post" id= ... >

We could also use ‘PHP_SELF’ instead of ‘SCRIPT_FILENAME’ however, as explained by Luke Visinoni in this article, this variable 'cannot be trusted' as it is vulnerable to cross-site scripting attacks. Using ‘SCRIPT_FILENAME’ is a safer option.

Next we add the PHP validations just below the "body" tag:

<?php
   $ErrMsg = "";  
   //Execute only if the page has been posted back (not first time load)
   If ($_SERVER['REQUEST_METHOD'] == 'POST')
   {
      //Validate user input: username and password must not be blank  
      if ($_POST['txtUname'] != "" && $_POST['txtPassword'] != "")
      {
        //Authenticate the user
      }
      else 
      {
        $ErrMsg = "Please enter a username and password";  
      }
   }
?>

Since we are posting to the same page, we want to prevent validations and subsequent authentication logic from executing when the page is loaded for the first time. At line 5, the script checks the request method of the page and will only execute the rest if it is set to ‘POST’. Assuming that the client will use a GET method when requesting the page for the first time this method will work fine. There are other techniques which could be used but for the sake of simplicity we’ll stick to this method. I must say however that at this stage I have yet to find a ‘fool proof’ method for doing this check as all the ones I came across have some kind of drawback. I guess avoiding posting to the same page or better still use an MVC framework would be a better solution but for now checking the request method will do nicely.

2. Server-Side Validation & Authentication

The previous code listing includes the first validation I want to add to the form, i.e. making sure that both the username and password are not left blank. Looking at line 8, ‘$_POST’ is a global associative array of variables which are passed to the script whenever a page is posted. It will contain the values of all the input fields in the submitted form. In our case, ‘txtUsername’ and ‘txtPassword’ are the names of the text boxes (HTML input type=text) for the username and password respectively. The names of these fields are set as keys in the $_POST associative array through which we can get to the actual values (See my previous post for more information on associative arrays). If either the username or password is left blank, the script sets the ‘$ErrMsg’ variable to the appropriate error message which is then ‘echo’ed to the user further down the page:


As I mentioned earlier, I am assuming that I already have some registered users. By that I mean that I have hard-coded a username and password in the script to represent values returned from a database. I will come back to this script later on in the series when I set up a proper registration page and store user accounts on a MySQL database. At this stage I wanted to focus on how to redirect authenticated users to the ‘private’ area and how we can use cookies/sessions to ‘remember’ a user. So, building on the previous script:

…
//Validate user input: username and password must not be blank  
if ($_POST['txtUname'] != "" && $_POST['txtPassword'] != "")
{
   //Authenticate the user
   define ("User", 'guest');
   define ("Pass" , 'letmein');
   if($_POST['txtUname']==User And $_POST['txtPassword']==Pass)
   {
      header("Location: http://localhost/PHPLabs/private.php");
   }   
   else 
   {
      $ErrMsg = 'Incorrect username or password';
   } 
}
else 
… 

After checking that the username and password are valid (against my hard-coded values) the ‘header’ function is called which sends a raw HTTP header to the browser causing the redirection to the selected page (private.php in this case). Pretty simple, however there’s a catch or rather two of them. First, any code after the header function will still be executed! In our case this is not that big of a problem as the script is quite small, however larger scripts would take longer to execute and consume more resources. Depending on how popular your site is and how many hits/requests it gets, these ‘wasted’ resources could have a negative effect on performance. To avoid this problem we can simply call to the ‘die()’ function after the redirection which will prevent the rest of the script from being executed. Secondly, the ‘header()’ function must be called before any kind of output is sent to the browser, including HTML tags, echo and print functions, otherwise it will not work. However you can use PHP output buffering to avoid this:

//Authenticate the user
define ("User", 'guest');
define ("Pass" , 'letmein');
if($_POST['txtUname']==User And $_POST['txtPassword']==Pass)
{
   ob_start();
   echo “Testing output buffering”;
   header("Location: http://localhost/PHPLabs/private.php");
   ob_flush();     
}   
else 
{
   $ErrMsg = 'Incorrect username or password';
} 
… 

The ‘ob_start()’ function turns on output buffering which will prevent the script from sending any output – except for headers – until ‘ob_flush()’ is called, so in this case no error is reported.

3. Using Sessions

Up to this point we have successfully redirected the user to the ‘private.php’ page after entering a valid username and password. However, ‘private.php’ still needs to verify that the user has been authenticated; otherwise all requests made to the page would be accepted! We need a way of persisting the ‘authenticated’ state until the user logs out or closes the browser. Here’s where sessions can help. Sessions are a combination of a client-side cookie and a server-side cookie where the former only contains a reference to the latter. For this reason, sessions are more secure than traditional cookies since the client has no means of changing important data stored within the cookie. Take a shopping cart for instance. If the shopping cart data was stored in a cookie on the client, the user could easily change the price (for example) of a given item before checking-out! Using cookies in this scenario is bad enough, placing something such as the price within that cookie is just asking for trouble.

Back to our scenario, we want to store a flag indicating that the user has been authenticated within a session variable such that it is accessible by every page in our private or member’s area. Here’s how it’s done:

session_start();
…
//Authenticate the user
define ("User", 'guest');
define ("Pass" , 'letmein');
if($_POST['txtUname']==User And $_POST['txtPassword']==Pass)
{
   $_SESSION['loggedin'] = 1;
   $_SESSION['userid'] = $_POST[‘txtUname’];
   header("Location: http://localhost/PHPLabs/private.php");
   die()
}   
else 
{
   $ErrMsg = 'Incorrect username or password';
} 
… 

We first need to place a call to ‘session_start()’ at the beginning of our PHP script (line 1). This will inform PHP that we intent to use sessions and to expect the use of session variables. Next, we use two session variables to store the flag indicating that the user has been authenticated as well as the username itself. As you can see, session variables in PHP are stored in an associative array called $_SESSION.

We now want to make sure that the private.php page checks for these session variables. This will ensure that when requesting the private page directly the client is redirected to the login page for authentication:

session_start();

if (isset($_SESSION['loggedin']))
{
   echo "Welcome ".$_SESSION['userid']; 
}
else 
{
   header('Location: http://localhost/PHPLabs/Forms.php');
   die();
}

Once again we should start the script by calling the session_start() function to gain access to the session variables which are then checked. If the ‘loggedin’ variable is set, the user is granted with a welcome message but otherwise redirected to the login page.

Finally, I will add a ‘logout’ button that will end the user’s session gracefully. The button simply calls the logout.php script which closes the session and redirects the client to the login page:

<?php
   //logout.php;
   session_start();
   session_unset();
   header("Location: http://localhost/PHPLabs/login.php");
   die();
?>

It might look odd that the scripts calls the session_start() function just before calling session_unset(), however it will not work otherwise. Remember that session_start() not only checks for an existing session and creates one if it finds none but it also gives the script access to the session variables. In essence, session_unset() would have nothing to unset unless session_start() is called beforehand! A bit confusing at first bit it does make sense when you think about it.

Wrapping up


That's it for this post.  We have seen how to create a simple login form and how to add server side validations to validate user input and authenticate the user.  We've also seen how we can redirect the client to the private area after authentication and how to use sessions to our advantage.  I haven't implemented the "remember me" check box yet, but I will be doing so when discussing cookies and personalisation later on in the series.

Overall, this exercise is helping me gain a clearer picture of  what PHP is all about.  In it's raw state its much more low level than what I'm used to (ASP.Net) but I've found it to be simple to learn, easy to use and fast performing. Also, the sheer amount of information about PHP on the Web can be quite overwhelming and sometimes contradictory.  I've found that - just like seeking medical advice - it's best to get a second opinion before embracing a concept.  As stated in my previous post, PHP is easy to learn but it is even easier to learn coding it badly.

My next post will focus on how to connect our PHP pages to a MySQL database and how to authenticate our users properly against that database.

Leave a Reply