AJAX with CSRF Protection in Codeigniter 2.0
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
- you've enabled it in config.php, and
- 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:
- You must include the
csrf_token_name
value as a parameter in your AJAX request - 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!
- Michael Reichner's blog
- Login to post comments
Awesome! Thank you!
Awesome! Thank you!
Works but not as the original code
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
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?
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!
And that was with encrypted cookies. Thanks!
I couldn't get it to work
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!
You just saved me 3 hours of work and 3 hours of facepalm. Thanks!
But... it breaks after refreshing the page
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
thank you T_T !!
It appears that the POST var
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
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
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
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
That works, but I prefer to keep my js code segregated into a separate, static file
This does not work for me.
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
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
The cookie will be updated.
Thanks you saved me a lot of
Thanks you saved me a lot of messing around !
CSRF
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
See my reply to Joe Auty above
Thank you
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
Thanks, great tutorial.
Small question:
The 2 solution, does it work, when cookie is encrypted too?
not sure on that one
let me know how it works out for you
Ahhh ex gfs.... Life's CSRFs
Ahhh ex gfs.... Life's CSRFs
Thanks, it works.
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
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 -
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
in the input class
Alfonso
You are correct - it is the input class, not the session class
Seotons
Thanks this site for everything. I 've just make my ex girlfriend to return with me. Thanks , thanks , thanks