Showing posts with label codeigniter. Show all posts
Showing posts with label codeigniter. Show all posts

Sep 1, 2012

CodeIgniter web app authentication with PostgreSQL and TankAuth

Some of you may note that I had earlier created my own simple authentication system in CodeIgniter. However there comes a time when you just need something fully featured by you don't have the time create it yourself; step into TankAuth (it is also a hosted project on GitHub).

This post will focus on how to get TankAuth working with PostgreSQL .

Some problems....

TankAuth was designed around a MySQL database, and hence some SQL commands won't work. This means that you will get some odd behavior; unfortunately fixing all of these issues is outside the scope of this post.

Some Prerequisite knowledge...

I am going to dive right into the deep end here so if you don't know anything about PostgreSQL, PHP, CodeIgniter or Relational Database theory I strongly suggest you go and do some quick research.

I have a series on PostgreSQL that include an installation guide for Ubuntu, useful functions and operators, a guide to improving Postgre performance, how to check for installed procedural languages, and an overview of some basic concepts and some more advanced ones.

You can also check out my notes on designing relational databases. As for PHP, you can check out my blog posts such as my AJAX with JQuery and PHP tricks. You can also check out this list of useful CodeIgniter Tutorials.

Setting up PostgreSQL

We are going to assume that you have already installed PostgreSQL and have correctly download and setup CodeIgniter (and it is correctly serving the default web page). Make sure you have installed the PHP PostgreSQL plug-ins (php5-pgsql and php5-odbc).

Use this script to check if you have correctly set-up PostgreSQL and PHP.

Open up a terminal and create a user and database for CodeIgniter and TankAuth like we did in this earlier post:

createuser -h localhost -U postgres tankauth -W -S -D -R -P
createdb -h localhost -U postgres -O tankauth -W  tankauthdb
NOTE: These commands assume that you have password protected the postgres user; if you have a default install you can ditch the -W trigger.


Once we have created a user and a database, connect to the database server via the terminal command:



psql -h localhost -d tankauthdb -U tankauth -W


Now create a schema for our project to use. In the psql terminal run the following:

tankauthdb=> CREATE SCHEMA codeigniter;

PostgreSQL is now prepped and ready for CodeIgniter...


Setting up CodeIgniter

To get CodeIgniter working with PostgreSQL you will simply need to edit two configuration files. First, edit ./application/config/database.php with the following:


$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'tankauth';
$db['default']['password'] = 'tankauth';
$db['default']['database'] = 'tankauthdb';
$db['default']['dbdriver'] = 'postgre';

$db['default']['dbprefix'] = 'codeigniter.';


Now we will autoload the database (just because it is easier than calling it for every controller); edit ./application/config/autoload.php with:

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

And that's it! CodeIgniter will now connect to PostgreSQL.

Setting up TankAuth

  1. Download and extract TankAuth.
  2. Copy the application folder content to your CI application folder.
  3. Copy the captcha folder to your CI folder. Make sure this folder is writable by web server.
  4. Open the application/config/config.php file in your CI installation and change $config['sess_use_database'] value to TRUE.
  5. Create an encryption key in application/config/config.php and editing $config['encryption_key']
  6. Finally turn off captcha's in by editing application/config/tank_auth.php with $config['captcha_registration'] = FALSE;
Now we will need to create the databases that TankAuth requires. First we will create a table for storing CodeIgniter sessions:

CREATE TABLE codeigniter.ci_sessions
(
  session_id character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
  ip_address character varying(16) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
  user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_activity integer NOT NULL DEFAULT 0,
  user_data text COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  CONSTRAINT ci_sessions_pkey PRIMARY KEY (session_id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.ci_sessions
  OWNER TO tankauth; 



This will create a table for recording login attempts AND a function and trigger for automatically updating the timestamp:


CREATE TABLE codeigniter.login_attempts
(
  id serial NOT NULL,
  ip_address character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  login character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  "time" timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT login_attempts_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.login_attempts
  OWNER TO tankauth;
CREATE OR REPLACE FUNCTION codeigniter.update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.time = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_login_attempts_modtime BEFORE UPDATE ON codeigniter.login_attempts FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_column();


This will create a table for tracking users who choose to use the autologin feature (and the requisite triggers):


CREATE TABLE codeigniter.user_autologin
(
  key_id character(32) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  user_id integer NOT NULL DEFAULT 0,
  user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_login timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT user_autologin_pkey PRIMARY KEY (key_id, user_id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.user_autologin
  OWNER TO tankauth;

CREATE OR REPLACE FUNCTION codeigniter.update_modified_login()
RETURNS TRIGGER AS $$
BEGIN
    NEW.last_login = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_autologin_modtime BEFORE UPDATE ON codeigniter.user_autologin FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();


This will create our user profiles:


CREATE TABLE codeigniter.user_profiles
(
  id serial NOT NULL,
  user_id integer NOT NULL,
  country character varying(20) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  website character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  CONSTRAINT user_profiles_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.user_profiles
  OWNER TO tankauth;
And finally this is our users login table:


CREATE TABLE codeigniter.users
(
  id serial NOT NULL,
  username character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  password character varying(255) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  email character varying(100) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  activated smallint NOT NULL DEFAULT 1,
  banned smallint NOT NULL DEFAULT 1,
  ban_reason character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_password_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_password_requested timestamp without time zone DEFAULT NULL,
  new_email character varying(100) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  new_email_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
  last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
  last_login timestamp without time zone NOT NULL DEFAULT now(),
  created timestamp without time zone NOT NULL DEFAULT now(),
  modified timestamp without time zone NOT NULL DEFAULT now(),
  CONSTRAINT user_pkey PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE codeigniter.users
  OWNER TO tankauth;

CREATE TRIGGER update_users_login BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();

CREATE OR REPLACE FUNCTION codeigniter.update_modified()
RETURNS TRIGGER AS $$
BEGIN
    NEW.modified = now();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_users_modtime BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified();


And that is TankAuth set up!!!

One more thing...

For those keeping track, they may have noticed my warning about TankAuth using MySQL specific functions. To get the basic stuff working, you will need to do edit application/models/tank_auth/login_attempts.php and replace:

$this->db->or_where('UNIX_TIMESTAMP(time) <', time() - $expire_period);

with:

$this->db->or_where('extract(epoch FROM time) <', time() - $expire_period);
And you are good to go!

Aug 30, 2012

CodeIgniter: Dynamically setting the website's language

I have been writing up my own View subsystem for CodeIgniter that seperates the  View more clearly from the controller. I have also designed it with Language support in mind, but for that I had to do some minor edits to the default Language class.

Even though you can load a specific language file in another language, the system will load error messages in the default language. This led to some cases where system errors would be in English, but the site language was something else.

With my new system I can have my default site language can be dynamically set by a user cookie… see the following code:

$lang_temp = $this->input->cookie('language');
if ( $lang_temp )
{
     // NOTE: getDefault() is one of my core Lang.php modifications
     if (!($lang_temp === $this->lang->getDefault()))
     {
        // NOTE: setDefault() is one of my core Lang.php modifications
        $this->lang->setDefault($lang_temp);
     }
} else {
     // Set a cookie with system default
     $this->input->set_cookie('language', $this->lang->getDefault(), '7200', '.' . $_SERVER['HTTP_HOST'], '/', NULL, FALSE);
}
 
But to make the above code work I had to make some changes to the core:



<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author ExpressionEngine Dev Team
* @copyright Copyright (c) 2008 - 2011, EllisLab, Inc.
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/

// ------------------------------------------------------------------------

/**
* Language Class
*
* @package CodeIgniter
* @subpackage Libraries
* @category Language
* @author ExpressionEngine Dev Team
* @link http://codeigniter.com/user_guide/libraries/language.html
*/
class CI_Lang {

/**
* List of translations
*
* @var array
*/
var $language = array();
/**
* List of loaded language files
*
* @var array
*/
var $is_loaded = array();

/**
* Default language
*
* @var array
*/
var $default_lang;

/**
* Constructor
*
* @access public
*/
function __construct()
{
    $config =& get_config();
    $this->default_lang = ( ! isset($config['language'])) ? 'en' : $config['language'];

    log_message('debug', "Language Class Initialized");
}

// --------------------------------------------------------------------

/**
* Load a language file
*
* @access public
* @param mixed the name of the language file to be loaded. Can be an array
* @param string the language (english, etc.)
* @param bool return loaded array of translations
* @param bool add suffix to $langfile
* @param string alternative path to look for language file
* @return mixed
*/
function load($langfile = '', $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '')
{
    $langfile = str_replace('.php', '', $langfile);

    if ($add_suffix == TRUE)
    {
       $langfile = str_replace('_lang.', '', $langfile).'_lang';
    }

    $langfile .= '.php';

    if (in_array($langfile, $this->is_loaded, TRUE))
    {
       return;
    }

    if ($idiom == '')
    {
       $idiom = ($this->default_lang == '') ? 'en' : $this->default_lang;
    }

    // Determine where the language file is and load it
    if ($alt_path != '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile))
    {
       include($alt_path.'language/'.$idiom.'/'.$langfile);
    }
    else
    {
       $found = FALSE;

       foreach (get_instance()->load->get_package_paths(TRUE) as $package_path)
       {
          if (file_exists($package_path.'language/'.$idiom.'/'.$langfile))
          {
              include($package_path.'language/'.$idiom.'/'.$langfile);
              $found = TRUE;
              break;
          }
      }

      if ($found !== TRUE)
      {
         show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile);
      }
   }


    if ( ! isset($lang))
   {
      log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile);
      return;
   }

   if ($return == TRUE)
   {
      return $lang;
   }

   $this->is_loaded[] = $langfile;
   $this->language = array_merge($this->language, $lang);
   unset($lang);

   log_message('debug', 'Language file loaded: language/'.$idiom.'/'.$langfile);
   return TRUE;
}

// --------------------------------------------------------------------

/**
* Fetch a single line of text from the language array
*
* @access public
* @param string $line the language line
* @return string
*/
function line($line = '')
{
   $value = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line];

   // Because killer robots like unicorns!
   if ($value === FALSE)
   {
      log_message('error', 'Could not find the language line "'.$line.'"');
   }

   return $value;
}

/**
* Fetch Default language
*
* @access public
* @param void
* @return string Default language code
*/
function getDefault()
{
   return $this->default_lang;
}

/**
* Set Default language
*
* @access public
* @param string The default language
* @return void
*/
function setDefault($language)
{
   $this->default_lang = $language;
}

}
// END Language Class

/* End of file Lang.php */
/* Location: ./system/core/Lang.php */

I am sure you can find other uses for this code.....

My other posts on CodeIgniter include:

Jul 18, 2012

AJAX, JQuery, CodeIgniter and PHP

This is an extension of an earlier blog of mine, but this time I have built it using the CodeIgniter PHP framework. I have also spiced it up with a dynamic JQuery form (kudos to Charlie Griefer and this blog post for the code!)

If you want to get your hands dirty doing something a little harder then I suggest you check out my earlier blog on creating a login system with CodeIgniter.

If you are just getting started, have a look at my other blog posts, 'Ubuntu, LAMP and CodeIgniter' and the 'Extended JQuery Tutorial'.

Getting started

I am going to assume that you are working with a fresh installation of CodeIgniter that simply displays the welcome message. I am going to start from there so we don't get into any confusion!

First, unzip the CodeIgniter source into your web-server's root directory (if you followed my LAMP guide above, this will default to /var/www/). Rename the extracted folder to test (so in the default example, the CodeIgniter framework will be stored in /var/www/test/).

Now delete ALL the php files in /var/www/test/application/controllers and /var/www/test/application/views. We are going to start completely fresh!

The final step is to configure the framework to use our new controller (once we create it...), so edit the file /var/www/test/application/config/routes.php with the following line:
$route['default_controller'] = "home";
Now to make our main controller....

The Home page controller

  1. Create a file in /var/www/test/application/controllers named home.php
  2. Add in the following code:
    <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

    class Home extends CI_Controller {

        /**
         * Index Page for the home controller.
         *
         * Maps to the following URL
         *         http://example.com/
    index.php
         *    - or - 
         *         http://example.com/index.php/home
         *    - or -
         * Since this controller is set as the default controller in
         * config/routes.php, it's displayed at http://example.com/
         *
         * So any other public methods not prefixed with an underscore will
         * map to /index.php/home/<method_name>
         * @see http://codeigniter.com/user_guide/general/urls.html
         */
        public function index()
        {
            $this->load->view('view');
        }
    }

    /* End of file ajax.php */
    /* Location: ./application/controllers/home.php */
  3. That's it. All this does is load up our view, which is our next step.

The Home page view

  1. Create a file in /var/www/test/application/views named view.php
  2. Add in the following code:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <title>Test</title>

    <!-- JQuery code hosted by Google -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>

    <!-- Adding and Deleting form buttons -->
    <script type="text/javascript" src="form.js"></script>

    <!-- AJAX functionality -->
    <script type="text/javascript" src="ajax.js"></script>
    </head>

    <body>

    <form id="myForm">
    <div id="input1" style="margin-bottom:4px;" class="clonedInput">
    Name: <input type="text" class="name" name="name1" id="name1" />
    </div>

    <div>
    <input type="button" id="btnAdd" value="add another name" />
    <input type="button" id="btnDel" value="remove name" />
    <input type="button" name="submit" class="button" id="submit" value="Send" />
    </div>

    <div id="display">
    </div>
    </form>

    </body>
    </html>
  3. Our basic HTML page includes three JavaScript files (One is the JQuery API, the other two we write ourselves; see below). We have a form with an input text box and three buttons. The first two buttons add and remove input boxes from our form, while the last button performs our AJAX operation.

form.js - Dynamically adding form elements

  1. Create a file called form.js in /var/www/test/
  2. Add the following code:
    $(document).ready(function() {
                $('#btnAdd').click(function() {
                   

                    // how many "duplicatable" input fields we currently have

                    var num     = $('.clonedInput').length;                // the numeric ID of the new input field being added
                    var newNum  = new Number(num + 1);
     
                    // create the new element via clone(),
                    // and manipulate it's ID using newNum value
                    var newElem = $('#input' + num).clone().attr('id', 'input' + newNum);
     
                    // manipulate the name/id values of the input inside the new element
                    newElem.children(':first').attr('id', 'name' + newNum).attr('name', 'name' + newNum);
     
                    // insert the new element after the last "duplicatable" input field
                    $('#input' + num).after(newElem);
     
                    // enable the "remove" button
                    $('#btnDel').attr('disabled','');
     
                    // business rule: you can only add 5 names
                    if (newNum == 5)
                        $('#btnAdd').attr('disabled','disabled');
                });
     
                $('#btnDel').click(function() {
                   
    // how many "duplicatable" input fields we currently have
                    var num = $('.clonedInput').length;

                   
    // remove the last element
                    $('#input' + num).remove();
     
                    // enable the "add" button
                    $('#btnAdd').attr('disabled','');
     
                    // if only one element remains, disable the "remove" button
                    if (num-1 == 1)
                        $('#btnDel').attr('disabled','disabled');
                });
     
                $('#btnDel').attr('disabled','disabled');
            });
  3. This function attaches a JavaScript function to the add and remove button. The code is pretty well commented by Charlie Griefer, so if you want to know more I suggest you go visit his blog post.

ajax.js - AJAX with JQuery

  1.  Create a file called ajax.js in /var/www/test/
  2. Add the following code:
    $(document).ready(function() {
        // This will run when the item of id 'submit' is clicked
        $("#submit").click(function() {

        // Grabs the text input
    from the form
        var name = [];
        $(".name").each(function() { name.push($(this).val()) });

        // Create the key-value pair for POST

        var dataString = 'string=' + name.join(' ') + '';

        // This creates the AJAX connection
        $.ajax({
           type: "POST",
           url: "index.php/ajax",
           data: dataString,
           dataType: 'json',
           success: function(obj) {
              $('#display').html(obj.message);
           }
        });
        return false;
        });
    });
  3. This will send all of our dynamically generated form elements to index.php/ajax (which we will now create). Hopefully the code and comments are self explanatory; if you have any problems just shout out in the comments!

The AJAX controller

  1. Create a file in /var/www/test/application/controllers named ajax.php
  2. Add the following code:
    <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

    class Ajax extends CI_Controller {


       /**

        * Index Page for this controller.
        *
        * Maps to the following URL
        * http://example.com/index.php/ajax
        * - or -
        * http://example.com/index.php/ajax/index
        *
        * So any other public methods not prefixed with an underscore will
        * map to /index.php/ajax/<method_name>
        * @see http://codeigniter.com/user_guide/general/urls.html
       */
       public function index()
       {
           $txtValue = "We have recieved: " . $this->input->post('string');

           echo json_encode(array('message' => $txtValue));

       }
    }

    /* End of file ajax.php */

    /* Location: ./application/controllers/ajax.php */
  3. You should now be able to run this on your web-server and have a dynamic form that sends and recieves AJAX!

References:

Jul 11, 2012

Ubuntu 12.04, LAMP and CodeIgniter 2

This guide aims to install and use CodeIgniter on an Ubuntu machine. We will assume you have the default Ubuntu installation.

  1. Install Apache 2:
    sudo apt-get install apache2
  2. Install MySQL:
    sudo apt-get install mysql-server mysql-client
  3. Install PHP5 and the necessary libraries:
    sudo apt-get install php5-cli php5-mysql libapache2-mod-php5
  4. Download the CodeIgniter framework:
    wget http://codeigniter.com/download.php -O ~/CodeIgniter.zip
  5. Extract the framework to the default Apache2 web directory:
    sudo unzip ~/CodeIgniter.zip /var/www/
  6. You should now be able to navigate to your extracted folder from your web-browser. Hint: you will need to open firefox to http://localhost/<name-of-folder> which you can get by executing the following command:
    ls /var/www

References

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