Rails Forgery Protection Basics

Create a thought scaffold resource.

rails g scaffold thought content

The generated form partial uses the form_for tag.

<%= form_for(thought) do |f| %>
  <% if thought.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(thought.errors.count, "error") %> prohibited this thought from being saved:</h2>
      <ul>
      <% thought.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :content %>
    <%= f.text_field :content %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

If you view the source for the create thought form, you will see the authenticity token generated by Rails. You will be able to create new thoughts. The application controller by default uses protect_from_forgery with: :exception.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

Let's copy the generated html and remove the authenticity token so that we can experiment to learn about different Rails forgery protection strategies. Use the following html form for _form.html.erb file.

<form class="new_thought" id="new_thought" action="/thoughts" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" />
  <div class="field">
    <label for="thought_content">Content</label>
    <input type="text" name="thought[content]" id="thought_content" />
  </div>
  <div class="actions">
    <input type="submit" name="commit" value="Create Thought" data-disable-with="Create Thought" />
  </div>
</form>

If you try to create a thought, you will see the error messages: Can't verify CSRF token authenticity. and ActionController::InvalidAuthenticityToken in the log file.

Started POST "/thoughts" for 127.0.0.1 at 2017-03-29 11:02:31 -0700
Processing by ThoughtsController#create as HTML
  Parameters: {"utf8"=>"✓", "thought"=>{"content"=>"no form tag"}, "commit"=>"Create Thought"}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

We can use :null_session to avoid crashing and insert the record in the database, change the application controller to use:

protect_from_forgery with: :null_session

If you create a thought, you will see that we still get the error: Can't verify CSRF token authenticity. in the log file but the record gets inserted into the database.

  Started POST "/thoughts" for 127.0.0.1 at 2017-03-29 11:04:31 -0700
  Processing by ThoughtsController#create as HTML
    Parameters: {"utf8"=>"✓", "thought"=>{"content"=>"sdfasfsa"}, "commit"=>"Create Thought"}
  Can't verify CSRF token authenticity.
     (0.1ms)  begin transaction
    SQL (0.4ms)  INSERT INTO "thoughts" ("content", "created_at", "updated_at") VALUES (?, ?, ?)  [["content", "sdfasfsa"], ["created_at", 2017-03-29 18:04:31 UTC], ["updated_at", 2017-03-29 18:04:31 UTC]]
     (0.8ms)  commit transaction
  Redirected to http://localhost:3000/thoughts/3
  Completed 302 Found in 5ms (ActiveRecord: 1.3ms)

Change protect_from_forgery to use :reset_session.

protect_from_forgery with: :reset_session

You can see that it behaves in a similar way to :null_session.

  Started POST "/thoughts" for 127.0.0.1 at 2017-03-29 11:06:27 -0700
  Processing by ThoughtsController#create as HTML
    Parameters: {"utf8"=>"✓", "thought"=>{"content"=>"testing reset session"}, "commit"=>"Create Thought"}
  Can't verify CSRF token authenticity.
     (0.1ms)  begin transaction
    SQL (0.5ms)  INSERT INTO "thoughts" ("content", "created_at", "updated_at") VALUES (?, ?, ?)  [["content", "testing reset session"], ["created_at", 2017-03-29 18:06:27 UTC], ["updated_at", 2017-03-29 18:06:27 UTC]]
     (1.3ms)  commit transaction
  Redirected to http://localhost:3000/thoughts/4
  Completed 302 Found in 5ms (ActiveRecord: 1.9ms)

The reset_session will issue a new session identifier. Instead of using the browser to experiment with the different forgery strategy, we can use Curl command. It is faster. We can copy the data from the log file and use the following query:

curl -H "Content-Type: application/json" -X POST -d '{"thought"=>{"content"=>"testing reset session using curl"}, "commit"=>"Create Thought"}' http://localhost:3000/thoughts

This will fail with the error:

Started POST "/thoughts" for 127.0.0.1 at 2017-03-29 11:10:25 -0700
Error occurred while parsing request parameters.
Contents:
  {"thought"=>{"content"=>"testing reset session using curl"}, "commit"=>"Create Thought"}
  ActionDispatch::ParamsParser::ParseError (822: unexpected token at '{"thought"=>{"content"=>"testing reset session using curl"}, "commit"=>"Create Thought"}'):

We can use any online JSON Lint tool to validate the JSON. We need to replace the => with : as shown below:

  {
    "thought": {
        "content": "testing reset session using curl"
    },
    "commit": "Create Thought"
  }

We can now use the following command with a valid JSON.

curl -H "Content-Type: application/json" -X POST -d '{"thought":{"content":"testing reset session using curl"}, "commit":"Create Thought"}' http://localhost:3000/thoughts

We will be redirected.

<html><body>You are being <a href="http://localhost:3000/thoughts/5">redirected</a>.</body></html>

We can see in the log files the result of the request.

    Started POST "/thoughts" for 127.0.0.1 at 2017-03-29 11:15:07 -0700
    Processing by ThoughtsController#create as */*
      Parameters: {"thought"=>{"content"=>"testing reset session using curl"}, "commit"=>"Create Thought"}
    Can't verify CSRF token authenticity.
       (0.1ms)  begin transaction
      SQL (0.4ms)  INSERT INTO "thoughts" ("content", "created_at", "updated_at") VALUES (?, ?, ?)  [["content", "testing reset session using curl"], ["created_at", 2017-03-29 18:15:07 UTC], ["updated_at", 2017-03-29 18:15:07 UTC]]
       (1.0ms)  commit transaction
    Redirected to http://localhost:3000/thoughts/5
    Completed 302 Found in 4ms (ActiveRecord: 1.5ms)

In this article, you learned three different Rails forgery protection strategies. You can download the source code for this project from mood.

References

Understanding Rails' Forgery Protection Strategies
Ruby on Rails Security Guide


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Rails Forgery Protection Basics


Create your own user feedback survey