Mar 29, 2012

CodeIgniter: PHP web app authentication

I have recently begun using CodeIgniter as my PHP framework for quickly whipping up web applications. And as a continuation on my web application blog posts (such as my AJAX with JQuery and PHP tricks) I have decided to whip this one up about how to use the CodeIgniter framework to create a site wide authentication system.

This tutorial assumes some knowledge in database administration and basic knowledge of CodeIgniter (time of writing I was using version 2.1.0) and PHP (version 5.3.10).

Model

First we will need to know the data we will be saving. The following SQL script was created using MySQL, but you should be able to configure with some minor modifications:

CREATE TABLE IF NOT EXISTS `users` (
  `userID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT 'The unique ID',
  `username` varchar(64) NOT NULL COMMENT 'A unique username',
  `password` varchar(128) NOT NULL COMMENT 'The password with embedded salt',
  `personID` int(10) unsigned zerofill NOT NULL COMMENT 'Foreign key to person data',
  `active` tinyint(1) NOT NULL COMMENT 'Whether the user is activated',
  `creationDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'The date the user was created',
  PRIMARY KEY (`userID`),
  UNIQUE KEY `username` (`username`),
  KEY `personID` (`personID`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;



Now that we know what data we are storing, we can create our model in CodeIgniter. In the folder 'application/models/', create a file called auth_model.php.

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Login Authentication Model Class
 *
 * This is the model that will access authentication data (and provide some
 * functions).
 *
 * @package        Application
 * @subpackage    Models
 * @category    Models
 * @author         Almighty Olive
 * @copyright    Copyright (c) 2012, olivecodex.blogspot.com.au
 */

class Auth_model extends CI_Model
{
    /**
     * Modified constructor class
     */
    function __construct()
        {
            parent::__construct();
       
        // Ensure that sessions are loaded
        $this->load->library('session');
    }

    /**
     * Secure hash generation function.
     *     $input - the data to be hashed
     *     $salt - OPTIONAL: the salt to be used in the hashing function
     *
     * For more info about hashing, please see http://crackstation.net/hashing-security.html
     */
    function createHash($input, $salt = null)
    {
        // If no salt is passed then generate it
        if ($salt === null)   
        {
            //get 256 random bits in hex
            $salt = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
        }

        //Prepend the salt, then hash
        $hash = hash("sha256", $salt . $input);
   
        //store the salt and hash in the same string, so only 1 DB column is needed
        $final = $salt . $hash;
   
        return $final;
    }


    /**
     * De-hash function (provided it has been hashed like above)
     * Returns true if the hashed input matches what is stored in the database
     *
     *     $salthash - the salt and hash created by authed_hash (stored in your DB)
     *     $input    - the data to verify
     *     returns   - true if the password is valid, false otherwise.
     *
     * For more info about hashing, please see http://crackstation.net/hashing-security.html
     */
    function validate($input, $salthash)
    {
        // Extract the salt and hash from the stored string
        $salt = substr($salthash, 0, 64); //get the salt from the front of the hash
        $validHash = substr($salthash, 64, 64); //the SHA256

        // Create a hash using the input and salt
        $testHash = hash("sha256", $salt . $input); //hash the password being tested
   
        //If the hashes are exactly the same, the password is valid
        return $testHash === $validHash;
    }
   
    /**
     * For a given username and password combination, this function
     * will look-up the database and determine if the user can access
     * the system
     *     $username = the passed username
     *    $password = the passed password
     *    returns true if the user is active
     */
    function checkAuth($username,$password)
    {
        // Sets up the query parameters
        $this->db->select("*");
        $this->db->where("username",$username);
        $this->db->where("active",'1');

        // Executes the specified query
        $query = $this->db->get("users");

        // Checks that only one row exists
        if(($query->num_rows()>0) && ($query->num_rows()<2))
        {
            $data = array();

            // Grab the resulting row
                $data = $query->row();

            // Checks to see if the password is correct
            if ( $this->validate($password, $data->password))
            {
                    // Create a data array to save to the session session
                    $sessionArray = array( 'userID'=>$data->userID,
                        'username'=>$data->username,
                        'personID'=>$data->personID,
                        'main'=>array(),
                        'logged_in'=>'TRUE');
                    $this->session->set_userdata($sessionArray);

                    // Return from the function
                    return TRUE;
            }
        }

        // If we have arrived here, it means we were not successful
        return FALSE;
    }

    /**
     * Check if a session has already been created
     *    returns true if a session exists
     */
    public function check_session()
    {
        if ($this->session->userdata('userID') AND $this->session->userdata('logged_in')=='TRUE')
        {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    /**
     * Deletes all session data
     *    returns true if a session exists
     */
    public function logout()
    {
        $this->session->unset_userdata('userID');
        $this->session->unset_userdata('logged_in');
        session_destroy();
    }
}
?>


Custom Controller Class

CodeIgniter uses the MVC design pattern, so most of our processing will be through our Controllers. To make our application automatically check if a user has logged in (and redirect them to the login screen if they haven't), we will create a new parent controller class. ALL pages that need authentication will be required to be a child of our new class!

Create a new file in 'application/core/' called 'MY_Controller.php' (change MY_ to whatever you have set it in your config file).

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
 * Application Controller Class
 *
 * This class object is a super class
 *
 * @package        CodeIgniter
 * @subpackage    Libraries
 * @category    Libraries
 * @author        ExpressionEngine Dev Team
 * @link        http://codeigniter.com/user_guide/general/controllers.html
 */

class MY_Controller extends CI_Controller
{
    function __construct()
    {
    // Performs all the initilisation for a controller
        parent::__construct();

    // Ensure that sessions are loaded
    $this->load->helper('url');

    // Ensure that sessions are loaded
    $this->load->library('session');

    // Load our Authentication Model
    $this->load->model('Auth_model');

    // Check our session to see if we are logged in or not
    if(!$this->Auth_model->check_session()){
        redirect('/auth/login');
    }
    }
}

// END Controller class

/* End of file MY_Controller.php */
/* Location: ./application/core/MY_Controller.php */


Login page controller

Now we need to create a login controller. Note that this controller is NOT derived from our custom class! The custom class redirects to our Login page, so if you do this then you will just end up with a continuous loop!

Create a new file under 'application/controllers/auth/' (create the folder if necessary) called 'login.php'. Note that I am using three view files; header.php, footer.php and login.php. I won't bother making these files as they should be pretty straightforward; you just need a simple form (that redirects to the login page) with a username field, a password field and a submit button.

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Login extends CI_Controller {

    /**
     * Constructor for this controller.
     *
     * Load up some common modules
     *
     * @see http://codeigniter.com/user_guide/general/urls.html
     */
    function __construct()
        {
        // Performs all the initilisation for a controller
            parent::__construct();

        // Loads the URL helper (redirection)
        $this->load->helper('url');

        // Ensure that sessions are loaded
        $this->load->library('session');

        // Load our Authentication Model
        $this->load->model('Auth_model');
        }

    /**
     * Index Page for this controller.
     *
     * Maps to the following URL
     *         http://example.com/auth/login
     *    - or - 
     *         http://example.com/auth/login/index
     *
     * @see http://codeigniter.com/user_guide/general/urls.html
     */
    public function index()
    {
        // Loads the form helper
        $this->load->helper('form');

        // Loads the form validation library
        $this->load->library('form_validation');

        // Check our session to see if we are logged in
        if($this->Auth_model->check_session())
        {
            redirect('/');
        }

        // Set the validation rules for the form
        $this->form_validation->set_rules('username', 'Username', 'trim|required|min_length[3]|xss_clean');
        $this->form_validation->set_rules('password', 'Password', 'required');

        // Run Validation check
        if($this->form_validation->run() == TRUE){
            // Grab our submitted data
            $username = $this->input->post('username');
            $password = $this->input->post('password');

            // Check if the user details matches that in our database
            if( $this->Auth_model->checkAuth($username,$password))
            {
                // Authentication is successful
                redirect('/');
            }
        }

        // If we are here it means authentication has failed... load up the error page
        $this->load->view('slair_header');
        $this->load->view('slair_login');
        $this->load->view('slair_footer');
    }

    /**
     * Logout Page for this controller.
     *
     * Maps to the following URL
     *         http://example.com/auth/login/logout
     *
     * @see http://codeigniter.com/user_guide/general/urls.html
     */
    public function logout()
    {   
        // Perform the logout functions
        $this->Auth_model->logout();

        //Goes back to the login screen
        redirect('/auth/login/');
    }
}

/* End of file slair.php */
/* Location: ./application/controllers/slair.php */


Conclusion

You should now be able to just do the following on any controller that requires authentication to access:

class CLASSNAME extends MY_Controller {

     ....

}


The MY_Controller will automatically redirect to the Login page if no session has been detected, otherwise we have a valid user so it will continue processing the rest of the PHP script.

Enjoy!

References:

  • Blog post from Afruj Jahan about writing a simple Login authentication system in CodeIgniter
  • CodeIgniter website
  • List of useful CodeIgniter Tutorials
  • Article about why we should ALWAYS salt our passwords
  • Article about the best way to secure your passwords from Cracked Station

2 comments:

  1. thanks Mr. Nassar, always fun with this methods.

    ReplyDelete
  2. Nice Tutorial...i got useful information from this tutorial,here is a way to findsimple registration form using codeigniter

    ReplyDelete

Thanks for contributing!! Try to keep on topic and please avoid flame wars!!