Building a Complete CodeIgniter Application: Part 4

Jim O'Halloran • September 5, 2010

php codeigniter feedignition

It's been a very long time since there was a new installment to this tutorial series (Jim from the future: No kidding! I don't know when I wrote this, but the nest date I can find is Sep 2010. I think that's when I moved the draft to a new wiki, I probably actually write this in 2008 or thereabouts. Fairly safe to say that when I wrote "it's been a long time, I didn't think 14 years type long time. But now it's 2021, and it's just seeing the light of day!), but we're back again with something new.

At the outset, I stated that we were going to build a multiuser feed reader. This means the one instance of our feed reader can serve many users, with each individual being able to log in and view their own feeds. To date we've built a proof of concept single user feed reader, but clearly the login and authentication mechanism is very important to the overall system.

CodeIgniter developers are blessed with a number of different authentication libraries (FreakAuth, ErkanaAuth, EzAuth and many others) which over varying levels of functionality. However, it seems like all we've done to date is integrate third party libraries, so lets build our own user authentication system. It's not actually that hard, and I like to use the hook and filters system for authentication. So let's take a look at those aspects of CodeIgniter.

Sessions

If we're going to do user logins, we somehow need to track whether the user has logged in or not. The obvious way to do this is using sessions, and for this task I prefer the Native Session library by Dariusz Debowczyk. Native Session is a PHP wrapper for the PHP session functions. It won't work on shared hosting servers where the PHP session functions aren't available. Native Session has one significant advantage over the normal CI session library, it doesn't send the entire session data to the browser in a cookie. This prevents users from seeing the contents of their session, it also neatly avoids cookie size limits, and saves bandwidth.

Setting up Native Session is pretty easy, just obtain the Native Session class from the CI Wiki, and drop it in the system/application/libraries folder. We'll need to use the session functions on virtually every page, so let's autoload the session library rather than doing it manually every time. Open system/application/config/autoload.php and find the line:

$autoload['libraries'] = array('database');

... and change it to ...

$autoload['libraries'] = array('database', 'session');

Filters

We'll use the filter system later on to restrict access to our feed reader to logged in users. First we'll want to obtain the filters system from the CI Wiki. Unzip the filters system and place the filter folder in system/application/hooks. There will also be a filters folder in the zip file, place that in system/application. The filters system attaches to the CI page rending process using Hooks, and allows the developer to create classes which operate on certain URLs. We'll use this later to make sure the user is logged in before allowing them to access certain controllers.

Hooks are turned off by default, so we'll need to enable them. Open system/application/config/config.php, and look for this line...

$config['enable_hooks'] = FALSE;

.. and change it to ...

$config['enable_hooks'] = TRUE;

Lastly, we'll want to set up the hooks used by the filters system. My config is slightly different to standard. Create a file in system/application/config/ called hooks.php with the following contents:

<?php if (!defined('BASEPATH')) {exit('No direct script access allowed');}
/*
 | -------------------------
 | Hooks
 | -------------------------
 | This file lets you define "hooks" to extend CI without hacking the core
 | files. Please see the user guide for info:
 |
 | http://www.codeigniter.com/user_guide/general/hooks.html
 |
*/

$hook['post_controller_constructor'][] = array(
    'class' => '',
    'function' => 'pre_filter',
    'filename' => 'init.php',
    'filepath' => 'hooks/filters',
    'params' => array()
);

$hook['post_controller'][] = array(
    'class' => '',
    'function' => 'post_filter',
    'filename' => 'init.php',
    'filepath' => 'hooks/filters',
    'params' => array()
);
?>

Clean Up

Before we get stuck into our user authentication system, lets tidy up a few things from earlier. First set the feeds controller to be the default controller, later we'll use a page from the feeds controller as the home page on our system. Open system/application/config/routes.php and look for the line beginning with $route['default_controller'], and change it to....

$route['default_controller'] = "feed";

We'll also want to display "Logged in as Jim O'Halloran (log out)" at the top of every page once a user is logged in. Open systam/application/views/header.php, and add the following code immediately after the body tag...

<?
$CI = &get_instance();
$user = $CI->session->userdata('user');
if ($user !== false) {
    ?><div class="loggedin">Logged in as <?=htmlentities($user['first_name'].' '.$user['last_name']); ?> (<a href="<?=site_url('user/logout'); ?>">logout</a>)</div>
<? }
?>

This code fragment uses the session library to get the user information from the session. The session library's userdata() function returns false if the item doesn't exist in the user session, and we'll rely on this to tell whether a user is logged in. If the $user variable isn't false, someone has logged in, so we'll output a div with the user's information. Note the use of htmlentities() to avoid Cross Site Scripting (XSS) problems with the user info. Also notice the use of the site_url function to create an absolute path for the log out page we'll create later. Later we'll also use CSS to style this div nicely, for the moment it's ugly but works.

Basic User Model

Lets get started by creating a database table for our users. We just need simple username, password, first name, last name, and email fields. We'll also add in an autoincrementing id field to provide a unique ID field. Use your favorite MySQL admin tool to run the following SQL against the database we created back in part 1:

CREATE TABLE `users` (
    `id` int(11) NOT NULL auto_increment,
    `username` varchar(30) default NULL,
    `password` varchar(81) NOT NULL,
    `email` varchar(100) NOT NULL,
    `first_name` varchar(50) NOT NULL,
    `last_name` varchar(50) NOT NULL, PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

We'll both salt and hash the passwords we store in the database, so the password field is 81 characters long to allow for two SHA1 hashes and a delimiter. Next we'll start by creating a model class which can load and save data from this table. I like to use a class which has member variables for each of the fields, and load(), save() and reset() methods for retrieving, saving, and creating a new record. Create a file in system/application/models/usermodel.php, and start with the following class definition...

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class UserModel extends Model {

    private $_id = false;
    public $username = '';
    public $password_hash = '';
    public $email = '';
    public $first_name = '';
    public $last_name = '';

    function __construct() {
        parent::Model();
    }

}
?>

At this point we've got a pretty useless model class with member variables for each of the database fields. I make the $id field private because I don't want controllers to be able to change the id value. I also place an underscore (_) in front of private variables to indicate their scope throughout the code, this isn't required, but it's a useful convention. I've also renamed the password field to password_hash in the model. The reason for this will become clear later on.

Next we'll implement a read only property for the id field. Place these two functions below the class constructor:

    function __get($var) { 
        switch ($var) { 
            case 'id':
                return $this->_id; 
        } 
    }

    function __set($var, $value) { 
        switch ($var) { 
            case 'id':
                throw new Exception('id is not a settable property of UserModel'); 
            default:
                $this->$var = $value; break; 
        } 
    }

The __get function is a magic method which PHP will call whenever we try to access a member variable which doesn't exist in the class. The name of the member variable that we attempted to access is passed as the first parameter of the __get method. We'll set up a switch statement here to test this value and return the _id member variable if the caller wanted the id property. I've used a case statement because we'll add a extra variiables later. The __set function is also a magic method, this one is called whenever we try to assign to a member variable which doesn't exist. Two parameters are passed to __set, the name of the variable, and the value that was assigned. We'll once more set up a switch structure to test the variable name and take action. We want to prevent users of the class from changing the id, so if someone tries to assign to our fake id property, we'll throw an exception. All other variables we'll just assign directly to the class, to ensure CI can still do its thing.

Now we want to provide a method to retrieve a user from the database, drop the following function into the model below the __set method:

    function load($id_or_row) { 
        if (is_array($id_or_row)) { 
            $row = $id_or_row; 
        } elseif (is_numeric($id_or_row)) { 
            $rs = $this->db->query('SELECT * FROM users WHERE id = ?', array($id_or_row)); 
            if ($rs->num_rows() > 0) { 
                $row = $rs->row_array(); 
            } else { 
                return false; 
            } 
        } else { 
            $rs = $this->db->query('SELECT * FROM users WHERE username = ?', array($id_or_row)); 
            if ($rs->num_rows() > 0) {
                $row = $rs->row_array(); 
            } else { 
                return false; 
            } 
        }

        $this->_id = $row['id']; 
        $this->username = $row['username']; 
        $this->password_hash = $row['password']; 
        $this->email = $row['email']; 
        $this->first_name = $row['first_name']; 
        $this->last_name = $row['last_name']; 
        return true; 
    }

I like my load methods to be fairly flexible. This one can accept either a numeric value containing the ID to load, a string containing the username, or an array containing all of the field values. The latter is handy when you query the database for lots of users and want to create a model object from each because then you don't need to query the database again for each record.

So we can load from the database, now what about saving. Here we'll set up a save method which handles the creation of either an update or an insert statement depending on the record's _id field. We use the boolean false value to denote a record which hasn't yet been assigned. Note: You could use ActiveRecord for this, but I've been writing SQL for years, and I'm more comfortable with that, so that's what I'll do.

The code for the save function is as follows, place it below the load function in our class:

    function save() { 
        if ($this->_id === false) { 
            $this->db->query("INSERT INTO users(username, password, email, first_name, last_name) VALUES(?, ?, ?, ?, ?)",
                array($this->username, $this->password_hash, $this->email, $this->first_name,
                $this->last_name)); 
            $this->_id = $this->db->insert_id(); 
        } else { 
            $this->db->query("UPDATE users SET username=?, password=?, email=?, first_name=?, last_name=? WHERE id=?", 
                array($this->username, $this->password_hash, $this->email, $this->first_name, $this->last_name, $this->_id)); 
        } 
    }

Finally, I like to have a reset method which resets the member variables back to defaults, this makes adding multiple records to the database easier. Here's my reset method:

    function reset() { 
        $this->_id = false; 
        $this->username = ''; 
        $this->password_hash = ''; 
        $this->email = ''; 
        $this->first_name = ''; 
        $this->last_name= ''; 
    }

Now we've got some basic model functionality which we'd use for any model where we want CRUD functionality.

Handling Passwords

Storing passwords is always a sensitive issue. Some people just store them in the database, but the problem here is that if an attacker can get a copy of the database, they can log in as any user on the system. You can usually tell systems which have been designed in this way because the lost password routine actually sends you your password in an email. This is obviously not ideal from a security perspective.

Then once people have figured out that plain text passwords are a bad idea, they'll usually MD5 the passwords before putting them in the database. MD5 is a one way hasn algorithm, data goes in and a (generally) unique hash comes out. It's not possible to obtain the original password from it's MD5 hash without using brute force (hashing all possible passwords), which makes it impossible to give back the original password via a "lost password" email. Simple MD5'ing is ok, but has two problems. First, tables of hashes for a large number of possible passwords (rainbow tables) are available on the Internet which make brute forcing MD5'ed passwords easier. Secondly, two users with the same password have the same hash stored in the database.

A minor step up from MD5'ed passwords is to hash with SHA1 instead. The SHA1 has is longer, making the rainbow tables for SHA1 much bigger, and therefore reversing SHA1 passwords slightly more resource intensive.

The approach I want to take however, is a salted SHA1 hash. Salting a password adds some random data to the original password. The salt and the hashed salted password are then stored in the database. Salting passwords makes a rainbow table attack less practical because the rainbow table would need to contain hashes for all possible passwords and all possible salts (an exponentially bigger table). As no two users will have the same salt, the password hash stored for two users with the same password will also be different. Salted SHA1 passwords aren't impenetrable, but they're better than the alternatives discussed above, and it's what we'll use on this project.

Jim from 2021 here again: SHA-1 was broken and considered insecure in 2020. SHA-256 would the be minimum today. Or better yet, use PHP's native password hashing functions.

We'll use the "password" field in the database for storing both the salt and the hashed password. We'll store them in the format "$". Assuming we've already got one of these in the database, the first thing we need is a function to retrieve the salt portion. For convenience, if the extract_salt function doesn't find a hashed password in the password_hash property, it'll create a new salt. Here's my extract_salt() and generate_salt() functions, place these at the bottom of the UserModel class before the closing }:

    private function extract_salt() { 
        if (strlen($this->password_hash) == 0) {
            return $this->generate_salt(); 
        } else { 
            $bits = explode('$', $this->password_hash); 
            if (count($bits) < 2) { 
                return $this->generate_salt(); 
            } else {
                return $bits[0]; 
            } 
        } 
    }

    private function generate_salt() { 
        return sha1(uniqid()); 
    }

These functions are private because a controller or view should never need to call them directly. We'll also need a function which can be used both to hash a new password, and validate an existing password. My hash_password function first uses the extract_salt method defined above to obtain a salt. Here we rely on the fact that extract_salt will generate a new salt if one doesn't already exist, then we combine the salt and the plaintext password together and call the PHP sha1() function on the combination to get the hashed password. Finally it returns both the salt and hashed password.

    private function hash_password($plaintext) { 
        $salt = $this->extract_salt();
        return $salt . '$' . sha1($salt.$plaintext); 
    }

Now to make the process more manageable, lets revisit the __get and __set magic methods. First we'll add a read only password_salt property to our users and make allowance for a write only password property:

    function __get($var) { 
        switch ($var) { 
            case 'id':
                return $this->_id; 
            case 'password_salt':
                return $this->extract_salt(); 
            case 'password':
                throw new Exception('password is not a gettable property of UserModel'); 
        } 
    }

Now we'll extend the __set magic method and create a write only property which can be used set a plain text password for the user without forcing the users of the UserModel class to understand how to properly salt a password.

    function __set($var, $value) {
        switch ($var) {
            case 'password':
                $this->password_hash = $this->hash_password($value);
            default:
                $this->$var = $value;
                break;
        }
    }

Conclusion

At this point, we've got a fully functional model class for our users, including a simple method to maintain a salted password hash. Next time we'll put this model class to use and build a signup form, a login form and we'll enforce logins for feed reading.