How to handle an expired CSRF token after a page is left open

I’m using CodeIgniter 2 along with the Ion Auth authorization system by Ben Edmunds.

After creating my project, I would sometimes get a CodeIgniter error upon certain login attempts but this error was intermittent.

The action you have requested is not allowed.

After some troubleshooting, it became apparent this error was caused by an invalid CSRF token. Why is the token invalid? Well, in CodeIgniter’s configuration file, it’s set to expire in 4 hours. So if you load your login page and allow it to sit there for 4 hours before attempting a login, the CSRF tokens will expire and this will generate the error message as above. Simply reloading the login page avoids any issues.

You can verify this error message for yourself by deleting the CSRF cookie after you load the login page.

A cleaner solution would be to redirect to a custom error page or to display a flash message. However, this solution is not as simple as it sounds because when you extend the CodeIgniter Security class, certain hook-points are not available and you cannot yet access CodeIgniter’s Super Object using `get_instance()`.

So when you extend the Security class, you’re limited to standard PHP. In this case, I’m using PHP `header()` to redirect the offending login page (or any form page) back to itself.

<?php
class MY_Security extends CI_Security {

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

    public function csrf_show_error()
    {
        // show_error('The action you have requested is not allowed.');  // default code

        // force page "refresh" - redirect back to itself with sanitized URI for security
        // a page refresh restores the CSRF cookie to allow a subsequent login
        header('Location: ' . htmlspecialchars($_SERVER['REQUEST_URI']), TRUE, 200);
    }
}

This works fine except that the user gets a screen refresh without any indication why they have to enter their login credentials a seconds time.

I decided to make this a bit more user-friendly by adding another function into a Controller, in my case, the Ion Auth controller…

function csrf_redirect()
{
    $flash = 'Session cookie automatically reset due to expired browser session.&nbsp; Please try again.';
    $this->session->set_flashdata('message', $flash);
    redirect('/login', 'location');
}

As you can see, this function sets a flash message telling the user what happened and then redirects them to a fresh instance of the login page.

Session cookie automatically reset due to expired browser session. Please try again.

Instead of using PHP `header()` to redirect a page refresh, redirect to this new Ion Auth controller function at `/auth/csrf_redirect`.

<?php
class MY_Security extends CI_Security {

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

    public function csrf_show_error()
    {
        // show_error('The action you have requested is not allowed.');  // default code

        // force redirect to the csrf_redirect function
        // this gives the user a useful message instructing them to login again
        // while the CSRF cookie is also refreshed to allow a new login
        header('Location: /auth/csrf_redirect', TRUE, 302);
    }
}

The minor downside to this method is that you are always redirected back to the login page rather than a refresh of whatever page/form you’re trying to submit. However, that should be a moot point, since the session cookie expires at nearly the same time as the CSRF cookie, you’d be redirected back to the same login page regardless. You may also not be requiring the user be logged in for your particular form, so please be aware and re-direct accordingly.

Extending the CodeIgniter Database Utility Class

You’d like to take advantage of CodeIgniter’s built-in database backup function as described here…

http://www.codeigniter.com/user_guide/database/utilities.html#backup

As you can see, only the `mysql` PHP database extension is supported. However, since the `mysql` PHP database extension has been deprecated, maybe you’re using another PHP database extension like `mysqli` instead. Now the problem is that you can no longer use CodeIgniter’s built-in database backup function without getting this error…

Unsupported feature of the database platform you are using.

The error simply means that if you use any PHP database extension besides `mysql`, there is no function included within any of CodeIgniter’s database drivers’ utility file for doing the backup.

No problem, we’ll just “extend” CodeIgniter’s database class.

Wrong! As per documentation,

Note: The Database classes can not be extended or replaced with your own classes.

Despite this limitation, there is a workaround below that will not involve editing CodeIgniter’s core system files.


Solution:
Instead, we’ll simply “extend” CodeIgniter’s Loader class. Within this custom Loader, we’ll only copy and slightly modify the `dbutil()` function. Study the function below and compare it to the original. I simply check for the existence of my custom utility driver file and load it in place of the default.

`application/core/MY_Loader.php` contains…

<?php class MY_Loader extends CI_Loader {

    public function dbutil()
    {
        // this function taken from CI v2.2.0
        // system/core/Loader.php
        // modified as indicated below

        if (! class_exists('CI_DB'))
        {
            $this->database();
        }

        $CI =& get_instance();

        // for backwards compatibility, load dbforge so we can extend dbutils off it
        // this use is deprecated and strongly discouraged
        $CI->load->dbforge();

        require_once(BASEPATH . 'database/DB_utility.php');

        // START custom >>

        // path of default db utility file
        $default_utility = BASEPATH . 'database/drivers/' . $CI->db->dbdriver . '/' . $CI->db->dbdriver . '_utility.php';

        // path of my custom db utility file
        $my_utility = APPPATH . 'libraries/MY_DB_' . $CI->db->dbdriver . '_utility.php';

        // set custom db utility file if it exists
        if (file_exists($my_utility))
        {
            $utility = $my_utility;
            $extend = 'MY_DB_';
        }
        else
        {
            $utility = $default_utility;
            $extend = 'CI_DB_';
        }

        // load db utility file
        require_once($utility);

        // set the class
        $class = $extend . $CI->db->dbdriver . '_utility';

        // << END custom

        $CI->dbutil = new $class();

    }

}

Now we need to find the database driver that we’re using. For `mysqli`, it should be located within `system/database/drivers/mysqli/mysqli_utility.php`. This file contains the `_backup()` function and you can see that it’s empty (“unsupported”).

Create an exact duplicate of this file; rename it `MY_DB_mysqli_utility.php` and place it here…

`application/libraries/MY_DB_mysqli_utility.php`

Everything within this file should remain the same as the original, except for the name of the `class` and the contents of the `_backup()` function…

<?php
    class MY_DB_mysqli_utility extends CI_DB_utility {
    
    // this code taken from CI v2.2.0
    // system/database/drivers/mysqli/mysqli_utility.php

    // everything in here is same as default mysqli_utility
    ....
    // EXCEPT the _backup() function contains my own code

    function _backup($params = array())
    {
        //  my custom working backup code
        ....

NOW you can put whatever code you see fit into this version of the `_backup()` function. I’m not going to tell you how to do this, but you could look inside of `system/database/drivers/mysql/mysql_utility.php` for some inspiration. You could also try the backup function suggested in this posting, which seems to be working.


The advantages to my technique are as follows…

– Updating the CodeIgniter system files leaves this solution intact.
– Changing the `dbdriver` setting in `config/database.php` will simply cause a fallback to the selected database utility driver. This solution is specific to one particular database driver (`mysqli`).
– Removing the `MY_Loader.php` file causes a fallback to the default database utility driver.
– Removing the `MY_DB_mysqli_utility.php` file causes a fallback to the default database utility driver.
– Update any other database utility driver by changing `mysqli` where-ever appropriate.

Note: This solution was successfully performed using CodeIgniter v2.2.0.