Software and Other Mysteries

On code and productivity with a dash of unicorn dust.

No-nonsense Protection Using a Nonce

UPDATE: A new and improved version of this extension can be found in my post Form protection revisited!

In a web application for record collectors I wrote a few years ago I recently had to deal with users who added the same record hundreds of times. I did use the PRG, or Post/Redirect/Get, pattern which means that you always redirect the user after a post request so that a simple reload won’t resubmit the same data. This prevents users from mistakenly resubmitting, but by pressing the back-button the problem re-emerges. The solution is called a nonce.

Actually, the use of a nonce also prevents an attack called CSRF, Cross-Site Request Forgery. Without going into detail, this means a malicious site can potentially perform CRUD operations with your credentials on a site you are currently logged in to. The idea to prevent this is to generate a nonce word that is sent with the form and stored at the server, making sure that the request is made using our form. This is basically what we wish to do:

  1. Generate nonce
  2. Save nonce in a session variable
  3. Add a hidden form field containing the nonce
  4. Validate data and nonce
  5. If nonce is valid, handle data and mark nonce as inactive

The key here is the validation of the nonce. There are two things to consider, one is that we should not allow the same form to be posted twice and the other is that the form must know what the current nonce is. This means for every request we need to have the current and, if it exists, old nonce saved in the user’s session. Also note I am assuming the sessions are stored in a database, since if they are not you probably have bigger problems.

To solve this problem I have extended the CodeIgniter Form Validation library so that it can handle this extra layer of security.

MY_form_validation.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
class MY_Form_Validation extends CI_Form_Validation {

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

    /**
     * Create a new unique nonce, save it to the current session and return it.
     *
     * @return string
     */
    function create_nonce()
    {
        $nonce = md5('nonce' . $this->CI->input->ip_address() . microtime());
        $this->CI->session->set_userdata('nonce', $nonce);
        return $nonce;
    }

    /**
     * Mark the nonce sent from the form as already used.
     */
    function save_nonce()
    {
        $this->CI->session->set_userdata('old_nonce', $this->set_value('nonce'));
    }

    /**
     * Set form validation rules for the nonce.
     */
    function nonce()
    {
        $this->set_rules('nonce', 'Nonce', 'required|check_nonce');
    }

  /**
  * Validation rule for making sure the nonce is valid.
  *
  * @access  public
  * @param   string
     * @param    last used nonce
  * @return  bool
  */
  function check_nonce($str)
  {
        return ($str == $this->CI->session->userdata('nonce') &&
                $str != $this->CI->session->userdata('old_nonce'));
  }

}

Also, I extended the form helper to include a function for generating a new hidden form field with a unique nonce:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
 * Generates a hidden field containing a nonce.
 *
 * @access   public
 * @return   string
 */
if ( ! function_exists('form_nonce'))
{
  function form_nonce()
  {
        $CI =& get_instance();
        $CI->load->library('form_validation');
      $field = '<input type="hidden" name="nonce" value="'
            . $CI->form_validation->set_value('nonce', $CI->form_validation->create_nonce())
            . '" />';
        return $field;
  }
}

Last but not least I’ll show you how to use it in a controller method, in this case called add.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
  function add()
  {
      $this->load->library('form_validation');
      // Set validation rules here..
        $this->form_validation->nonce();

      if ($this->form_validation->run() !== FALSE) { // If validation has completed
            $this->form_validation->save_nonce();
          // Handle the validated post request
          redirect('controller/method');
      }
  }

This is not a subject I’d consider myself to be an expert in, so any input on the validity of this library is greatly appreciated. This is something that might very well get included in future versions of CodeIgniter, and it should, but until then I hope this is a good alternative.

Comments