AJAX with CSRF Protection in Codeigniter 2.0

Submitted by Michael Reichner on Mon, 12/20/2010 - 02:19

Update: With the official CI 2.0 (Reactor) release, the names of the CSRF token cookie, and the hidden form input element, have been changed from ci_csrf_token to csrf_token_name. This post has been updated to reflect that change.

Codeigniter 2.0 adds an important security feature to prevent CSRF (Cross Site Request Forgery) attacks. Even better, the feature is automatically added to your forms, assuming that

  1. you've enabled it in config.php, and
  2. you're using the form_open() function from the Form Helper

Bastian Heist wrote a great post about CSRF, and Codeigniter's handling of it, a couple of weeks ago. I'll provide a very brief overview of the handling here, but I encourage you to go read Bastian's post.

The CSRF detection and protection occurs behind the scenes, in the course of normal form processing. An input type=hidden element is automatically added to the form by the form_open() function. When the form is submitted, the Security class looks for this element and attempts to match its value against a cookie bearing the same name. If they don't match, the form will be rejected.

Since this happens behind the scenes, your normal form processing will not require any remediation when you upgrade from CI 1.7.x to CI 2.0.

Note that I said "normal form processing". I came upon this knowledge when I converted a dev site from 1.7.2 to 2.0, only to find that all of my AJAX functions were returning 500 Internal Server Errors.

I hadn't a clue at the time what was causing this, and initially assumed that it was an issue with my hosting provider. Since it was a dev site, and I was entertaining a colleague, I ignored the issue until he left.

Then I noticed that the response header was a CI style error message - "An Error Was Encountered. The action you have requested is not allowed." So I posted a question to the CI forum and called it a night.

The next morning I found a reply directing me toward CSRF protection being the cause the issue and I started my research, ultimately ending up with Bastian's post.

Unfortunately, though, I didn't find much about remediating AJAX requests, so I was on my own. I came up with two solutions.

Method 1 - Interrogate the Hidden csrf_token_name Input Element

If you are only using AJAX in forms (autocompletes, cascading selects, checking the uniqueness of fields, etc), you can interrogate the csrf_token_name hidden input element and include that in your AJAX request. In jQuery, this might look like:


// get the token value
var cct = $("input[name=csrf_token_name]").val();


// add the token to the load request
$(".addform #state, .searchform #state").change(function () {
    var p = $("#state option:selected").val();
    if (p > 0) {
        $('#county').load('/ajax/counties/get', {'state_id': p, 'csrf_token_name': cct}, function (data) {this.value = data;});
    }
});

This worked perfectly, from within forms. But I use AJAX for much more than just forms. For example, following the principle of progressive enhancement, I use AJAX to intercept several classes of anchor tags and perform their work in place, rather than generating a new page. There is no form element on the pages that I'm intercepting these anchor tags from, so Method 1 is not available to me in these cases.

Method 2 - Interrogating the csrf_token_name Cookie

Since there is no form element to interrogate, I have to get the token from the cookie.

This is a little more involved, since jQuery doesn't natively provide cookie access. So, first, I had to download and install a cookie plugin from the jQuery site.

Then, getting the cookie was as easy as calling the function in the plugin

var cct = $.cookie('csrf_token_name');

And including it in AJAX requests worked the same as in method 1

$(".createlink").click(function(){
    var itag = '';
    var p = $(this).attr('href');
    $(this).load('/ajax/link/add', {'uri': p, 'csrf_token_name': cct});
    $(this).empty().replaceWith(itag);
    return false;
});

If you're using another library instead of jQuery, the specifics of the remediation will obviously differ. But the principles remain the same:

  1. You must include the csrf_token_name value as a parameter in your AJAX request
  2. You can get that token from the hidden element if you're using a form. Otherwise, you will have to get it from the cookie

So, cookie interrogation (method 2) is how I ultimately remediated all of my AJAX requests after upgrading to CI 2.0. As of now, it seems to be working flawlessly.

If you found another solution, please add it in the comments!

Awesome! Thank you!

Submitted by cesar (not verified) on Thu, 03/01/2012 - 08:54.

Awesome! Thank you!

Works but not as the original code

Submitted by Adamantus (not verified) on Thu, 02/23/2012 - 12:21.

As pointed out by Styopi this only works if you change the $.cookie() line to $.cookie('csrf_cookie_name').

It works with a refresh in my case but I suppose that is context dependant. I'm using it with the $.ajax() function with type:post set. I'm just using it to populate a box.

You are my man

Submitted by Maciej (not verified) on Sun, 02/05/2012 - 15:08.

Thank you mate, yoou are my man, I spent some time on this problem, and now you show me solution.
Thank you very much

csrf_token_name or csrf_cookie_name?

Submitted by Styopi (not verified) on Mon, 01/23/2012 - 19:21.

This solution does not work in my application:
var cct = $.cookie('csrf_token_name');
.....
$.post(action, {'csrf_token_name': cct, ...

This solution works:
var cct = $.cookie('csrf_cookie_name');
...
$.post(action, {'csrf_token_name': cct, ...

I got it to work!

Submitted by Mike (not verified) on Sat, 12/10/2011 - 11:08.

And that was with encrypted cookies. Thanks!

I couldn't get it to work

Submitted by Mike (not verified) on Sat, 12/10/2011 - 10:59.

I'm using encrypted cookies, and I don't know if that has anything to do with it. It works on the first request, but after that nothing.

Thanks a million!

Submitted by aditya menon (not verified) on Mon, 08/22/2011 - 02:23.

You just saved me 3 hours of work and 3 hours of facepalm. Thanks!

But... it breaks after refreshing the page

Submitted by Al (not verified) on Sun, 08/21/2011 - 03:03.

Everything works fine until I do a page refresh. Then it breaks again. If I do a hard refresh, it starts working again.

YOU ARE MY HERO

Submitted by Reiki (not verified) on Sun, 08/07/2011 - 09:50.

thank you T_T !!

It appears that the POST var

Submitted by Joe Auty (not verified) on Mon, 05/30/2011 - 04:19.

It appears that the POST var that needs to be sent via AJAX requests is now "ci_csrf_token" rather than "csrf_token_name"

Actually, it's whatever you

Submitted by admin on Mon, 06/06/2011 - 00:22.

Actually, it's whatever you have $config['csrf_cookie_name'] set to in config.php.

Further, there's a bug in 2.0.1, such that the cookie is actually given the name set in $config['csrf_token_name']. This error is corrected in 2.0.2, but that release has a whole other set of issues

A better way

Submitted by Shinhan (not verified) on Mon, 05/30/2011 - 04:05.

Or, alternatively, you could use $this->security->get_csrf_hash() method, like this:


$(".createlink").click(function(){
var itag = '';
var p = $(this).attr('href');
$(this).load('/ajax/link/add', {'uri': p, 'csrf_token_name': <?php echo $this->security->get_csrf_hash(); ?>});
$(this).empty().replaceWith(itag);
return false;
});

(This would have to be in .php file of course)

No

Submitted by Anonymous (not verified) on Mon, 01/30/2012 - 17:33.

Actually, this will only work if you plan to make only 1 ajax request, because the csrf token will be regenerated after each request. Therefore, when you attempt to make a second ajax request, your hard-coded csrf token will be outdated. If you check the cookie each time right before making a request you will always get the up-to-date token.

That works, but I prefer to

Submitted by admin on Mon, 06/06/2011 - 00:25.

That works, but I prefer to keep my js code segregated into a separate, static file

This does not work for me.

Submitted by Equalizer (not verified) on Wed, 05/25/2011 - 12:24.

This does not work for me. If you leave page open long enough for cookie to expire, the value is no longer retrievable, and the server does NOT set a new cookie like it would if the page were refreshed!

Won't this cause AJAX apps to

Submitted by Equalizer (not verified) on Tue, 05/24/2011 - 16:21.

Won't this cause AJAX apps to break though once the CSRF is regenerated server-side? Both the cookie and the hidden input's values will be outdated.

No

Submitted by Anonymous (not verified) on Mon, 01/30/2012 - 17:34.

The cookie will be updated.

Thanks you saved me a lot of

Submitted by Paul (not verified) on Mon, 05/16/2011 - 09:31.

Thanks you saved me a lot of messing around !

CSRF

Submitted by Anonymous (not verified) on Tue, 04/19/2011 - 12:40.

in second solution, i got null when i alert cct = $.cookie('csrf_token_name');

i have already load the cookie plugin and view source to make sure it loads.

See my reply to Joe Auty

Submitted by admin on Mon, 06/06/2011 - 00:28.

See my reply to Joe Auty above

Thank you

Submitted by Karthikeyan (not verified) on Thu, 04/14/2011 - 01:49.

Dear admin,
Thank you very much for this post..It is very helpful to me..Its work wonderful,,,,,,Again thank you so much..

Thanks, great tutorial. Small

Submitted by Martin (not verified) on Thu, 03/17/2011 - 06:32.

Thanks, great tutorial.
Small question:
The 2 solution, does it work, when cookie is encrypted too?

not sure on that one

Submitted by admin on Sat, 03/19/2011 - 12:55.

let me know how it works out for you

Ahhh ex gfs.... Life's CSRFs

Submitted by Anonymous (not verified) on Sat, 02/26/2011 - 21:53.

Ahhh ex gfs.... Life's CSRFs

Thanks, it works.

Submitted by Tarique Imam (not verified) on Tue, 02/15/2011 - 21:18.

After 2 hours of huge googling, i found your post is only the problem solver for the CI 2.0 with ajax while the CSRF is kept TRUE.

Really very good post. Thanks a lot.

Then after sending the csrf

Submitted by dianikol (not verified) on Wed, 02/09/2011 - 19:34.

Then after sending the csrf to the controller what are you doing next? What i need to do within the controller in order to succeed the ajax call???

danikol -

Submitted by admin on Tue, 02/15/2011 - 11:00.

you don't need to do anything in the controller. The security check is automatically done via the

$this->security->csrf_verify();

in the Session class.

in the input class

Submitted by Alfonso (not verified) on Sun, 06/05/2011 - 14:48.

in the input class

Alfonso

Submitted by admin on Sun, 06/05/2011 - 23:29.

You are correct - it is the input class, not the session class

Seotons

Submitted by sindunsuppy (not verified) on Wed, 01/12/2011 - 01:20.

Thanks this site for everything. I 've just make my ex girlfriend to return with me. Thanks , thanks , thanks