CSRF Protection with Ajax and Laravel

Roughly a 3 minute read by Chris Willerton

Sorry? Say that again...

CSRF stands for "cross site request forgery" and is a type of attack where a malicious website, email, instant message, or program causes a browser to perform an unwanted action on a website for which the user is currently authenticated. So, imagine if Facebook didn't protect against this kind of attack and you were logged into the site, there would be the potential for an external source to makes changes to your account, post to your wall or delete your content (amongst other things). Not good.

I see! What can be done?

Essentially what we will do is always send the CSRF token that Laravel generates across as a header in the Ajax request. This allows Laravel to validate the token server side when the request into the application is made.

Note: The below instructions have been tested with version 4.2 of Laravel. Similar principles will apply in Laravel 5, but where you place things might be different (middleware instead of filters comes to mind).

Pass the token in your Ajax headers

The below code needs to run on every page load and needs to be placed somewhere PHP will run. You could just put this in your main site template. This places the CSRF token that Laravel generates and adds it to the headers of the site's Ajax requests using jQuery's ajaxSetup method.

$.ajaxSetup({ headers: { 'csrftoken' : '{{ csrf_token() }}' } });

Note: The example above uses blade syntax so make sure you're using a blade.php file (why wouldn't you be?).

Modifying the existing CSRF filter

Open filters.php found in the root of the app folder. In here there is an existing CSRF protection filter that ships with Laravel. Change the filter to look like the below:

Route::filter('csrf', function() 
    if (Request::ajax()) 
        if (Session::token() !== Request::header('csrftoken')) 
            // Change this to return something your JavaScript can read...
            throw new Illuminate\Session\TokenMismatchException;
    elseif (Session::token() !== Input::get('_token')) 
        throw new Illuminate\Session\TokenMismatchException;

Basically this checks if the request is an Ajax request, and if so validates the token passed in the headers. If they don't match the one in Laravel's sessions then throw the usual exception. If the request isn't Ajax then use the normal check.

Note: As the comment in the example code above mentions, this could be made a bit nicer on your site, perhaps by passing an error back with JSON rather than throwing an exception. I'll let you figure that bit out though.

Final comments

  • This example relies on jQuery handling your Ajax requests. If you aren't using jQuery then you'll have to determine the best way to pass the token header in your Ajax requests.
  • The name of the header that is passed is csrftoken. This has been tested as "safe" to use. The reason I mention this is because originally I named it _token but Laravel seemed to strip it out of the headers. One day I might investigate this further and work out exactly what is happening here. I imagine it is something to do with Laravel using _token in other places.
  • As with non-Ajax post requests, you need to add the CSRF filter to your route in order for it to fire.