2

I have a simple application and I'm using Rails 5.0.0.1 (the app is called simpletest - it's as simple as I can make it) in order to figure some stuff out in Rails 5. I'm getting a weird error.

Here's config/routes.rb

Rails.application.routes.draw do
  root "foo#bar"
  get "dobaz" => "foo#bar"
  match "dobaz" => "foo#baz", via: :post
end

Here's app/controllers/foo_controller.rb

class FooController < ApplicationController
  def baz
      if session[:qux].nil? || params[:reset] == "true"
        session[:qux] = 0
      else
        session[:qux] += 1
      end
      @qux = session[:qux]
      @format = request.format
      render 'bar'

  end
end

And here's apps/views/foo/bar.html.erb

<p>Qux: <%= @qux %></p>
<p>Format: <%= @format %></p>
<form action="/dobaz" method="post">
<p>Reset:
<input type="radio" name="reset" value="true">True</input>
<input type="radio" name="reset" value="false">False</input>
<input type="submit"/>
</form>

I'm not using any authentication or login or anything else. I am (obviously) using the session.

When I try to actually execute the app, I get the following error on an exception page:

ActionController::InvalidAuthenticityToken in FooController#baz

I've checked a bunch of answers. I do have <%= csrf_meta_tags %> in apps/views/layouts/application.html.erb.

This only happens with post. With get, it works fine. However, I need to use post.

Further, there is a solution, but it doesn't make sense to me. I can add the following code to the top of my controller, which (if I understand it properly) should reset the session if the format is json, and suddenly it works:

protect_from_forgery with: :reset_session, if: ->{request.format.json?}

but the format is text/html! Also, the session isn't being reset (as I can tell via session[:qux]. Why would this have any effect? If I just use:

protect_from_forgery with: :reset_session

it works, but of course it resets the session. Using :null_session in place of :reset_session has the same effect (both with and without the if). I've done nothing special. Other than adding code, all I've done is:

rails new simpletest
[copied over Gemfile]
bundle install
rails generate controller foo bar

I'm running ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux] on Debian 8.

Joshua
  • 21
  • 5

2 Answers2

2

The problem is that you need to use a form helper which will automatically set up a hidden input for authenticty_token.

In you view you can just use form_tag (which is the simplest helper of this type)

<!-- in app/views/foo/bar.html.erb -->
<%= form_tag "/dobaz" do %>
  <p>Reset:
  <input type="radio" name="reset" value="true">True</input>
  <input type="radio" name="reset" value="false">False</input>
  <input type="submit"/>
<% end %>
  • Apparently I don't qualify for upvotes that count, but for future explorers, this solution works perfectly, and along with @cdimitroulas's answer, explains why `protect_from_forgery with: :reset_session, if: ->{request.format.json?}` works, even though it doesn't apply. – Joshua Dec 04 '16 at 15:50
1

As jphager2 mentioned this is an issue caused by Rails' protection against CSRF(Cross Site Request Forgery) when using input forms and can be easily solved by using a form helper instead of a simple HTML form.

You can see Faisal's great answer on this topic here -> Understanding the Rails Authenticity Token

Community
  • 1
  • 1
cdimitroulas
  • 1,576
  • 8
  • 17
  • Thanks, this really helps. There are many threads on this, and I didn't find this one (I was perhaps overfocused on the error message as search text). If I understand correctly, the reason `protext_from_forgery with:` works is that it forces the authentication token to be used. Does that seem correct? – Joshua Nov 29 '16 at 19:11
  • I'm fairly new to rails but as far as I understand that is correct! – cdimitroulas Nov 30 '16 at 16:01