CSRF Protection in Rails

In this post I will explain what CSRF is and how hackers can perform the attack. I’ll then cover what Rails does to protect against CSRF. Finally, I’ll cover how to get AJAX/JavaScript and APIs working with cross site forgery protection enabled.

CSRF

Cross Site Request Forgery (CSRF) is a type of hack where the attacker tricks the victim into clicking a link or visting a page that makes a request to the target site. The attack works if the victim is currently authenticated on the target application (for example, if they are signed into the application in another browser tab).

When you sign in to an application you are authenticated and the application sets a session cookie on your browser. This session cookie is sent with every HTTP request that you make. It allows the application to know that you have already logged in so that you don’t need to login again each time. CSRF exploits this, if you make a request from another site your browser will still send all of your session cookies. This means that the attacker does not need to do much work to authenticate themselves, they just need to construct a valid request and get the victim’s browser to perform it.

If the target site is not resistant to CSRF then the request will be treated as a normal request and the attacker will be able to update passwords, delete accounts or perform other melicous actions.

How to perform the attack

It’s hard to protect against an attack unless you know how it is performed. Here, I’m going to show you how to simulate a CSRF attack.

  1. Sign-in to your application which will cause a session cookie to be saved to your browser (in this example I am using devise for authentication)
  2. Open up a new tab and navigate to a totally different website, in this example I’ll navigate to jQuery.com
  3. Next, open up the console on your browser and write a JavaScript function that makes a POST request to your application.
  4. If you have protect_from_forgery enabled then you will get a 422 error with “Can’t verify CSRF token authenticity”. If you don’t have protect_from_forgery enabled then the POST will succeed and you have just performed a CSRF attack.

The screenshots above show CSRF attacks being performed. The first has protect_from_forgery enabled so the request returns a 422. For the second attempt I have commented out protect_from_forgery. As a result, the attack succeeds and the record is deleted.

Here you can see the same attacks from the perspective of the rails console.

How does Rails protect against CSRF

Out of the box, Rails includes the following line in your ApplicationController:

protect_from_forgery with: :exception

This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn’t match what was expected, an exception will be thrown.

Essentially, as long as you don’t override protect_from_forgery or remove it from your ApplicationController then you don’t need to think about it.

Ajax and JavaScript

All Rails 5 applications come with the csrf_meta_tags method in app/view/layouts/application.html.erb. This JavaScript to make POST requests without triggering CSRF protection.

The csrf_meta_tags method adds two meta tags to your page “csrf-param” and “csrf-token”. To make a POST request, extract the “csrf-token” from the meta-tag and send as the “X-CSRF-Token” HTTP header. However, if you are using jQuery with jquery-rails then this will happend automatically.

Here is an example to demonstrate this. The ‘Delete a Toy’ button triggers a jQuery function which makes a POST request to delete a record. In the first pair of screenshots I have csrf_meta_tags commented out. You can see that the request returns a 422 and CSRF error message.


This time I put csrf_meta_tags back in, reloaded the page and clicked the button to trigger the JS call. The screenshots below show that the two meta tags were sent automatically in the request and this time the request was successful.

APIs

APIs should be state-less, which means that authentication is handled on every request without the need to maintain a session. Therefore, you don’t want to add protect_from_forgery for API controllers because there is actually no risk of a CSRF attack.

Rails 5 allows you to create API only applications. By default, API-only applications inherit from ActionController::API instead of ActionController::Base and ActionController::API does not implement protect_from_forgery. So API-only applications work out of the box

For standard Rails 5 applications you can disable protect_from_forgery with the following code:

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

This assumes that your JSON endpoints are state-less.

Summary

We’ve covered what a CSRF attack is, how to similate an attack and how Rails helps to protect you against it with the protect_from_forgery method. We also covered using JavaScript with protect_from_forgery and how to prevent your APIs from being affected by protect_from_forgery.

You can checkout the whole example on Github. You can also watch the screencast about this topic.