Matteo Pagliazzi
Matteo Pagliazzi

Reputation: 5270

Rails and ajax request: not using csrf working?

I was Making a AJAX POST request to rails with this code:

  var new_note = {
    title: "New note"
  };
  $.post('/notes.json',
    {
      auth_token: auth_token,
      note: new_note
    },
    function(data, textStatus, jqXHR){
      console.log(textStatus);
      console.log(jqXHR);
      var createdNoteIndex = self.notes.push(new Note());
      self.openNote(self.notes()[createdNoteIndex - 1]);
    }, "json")
    .error(function(jqXHR, textStatus, errorThrown){
      alert("error");
      console.log(jqXHR);
      console.log(textStatus);
      console.log(errorThrown);
    });

and I forgot to insert the csrf token so I thought the create action was going to fail:

  # POST /notes.json
  def create
    @note = current_user.notes.new(params[:note])

      if @note.save
        respond_with { render json: @note, status: :created, location: @note }
      else
        respond_with { render json: @note.errors, status: :unprocessable_entity }
      end
  end

but the record in the database has been created anyway while the requested ended in a 500 error:

Started POST "/notes.json" for 127.0.0.1 at 2012-04-30 15:26:33 +0200
Processing by NotesController#create as JSON
  Parameters: {"auth_token"=>"zJzKxPnvx5dQDTcFWi5k", "note"=>{"title"=>"New note"}}
MONGODB (0ms) taccuino_development['users'].find({:_id=>BSON::ObjectId('4f9c670a809ad20869000002')}).limit(-1).sort([[:_id, :asc]])
MONGODB (0ms) taccuino_development['notes'].insert([{"_id"=>BSON::ObjectId('4f9e9309809ad223f5000007'), "title"=>"New note", "user_id"=>BSON::ObjectId('4f9c670a809ad20869000002')}])
Completed 500 Internal Server Error in 8ms

AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".):
  app/controllers/notes_controller.rb:26:in `create'


  Rendered /home/matteo/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.erb (4.2ms)
  Rendered /home/matteo/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.5ms)
  Rendered /home/matteo/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (14.8ms)

I've not disabled the csrf protection so It should have given an error about the missing of the token but it hasn't...

EDIT:

after readng the two answers I have:

added this code to replace the jquery_ui function for csrf token and setting also the auth_token for devise:

  $.ajaxSetup({
    beforeSend: function(xhr, settings) {
      if (settings.crossDomain) return;              
      var csrf_token = $('meta[name="csrf-token"]').attr('content');
      var auth_token = $('meta[name="auth_token"]').attr('content');

      xhr.setRequestHeader('X-CSRF-Token', csrf_token);
      xhr.setRequestHeader('auth_token', auth_token);
    }
  });

removed the before_file authenticate_user! from the controller and replaced the create action releated to the current_user with a different one:

  def create
    @note = Note.new(params[:note])

      if @note.save
        respond_with { render json: @note, status: :created }
      else
        respond_with { render json: @note.errors, status: :unprocessable_entity }
      end
  end

Then I've disabled the CSRF protection but i'm still getting the same error... so the probelm is another but i really can't understand what can cause a double redirection since the record is correctly created in the database...

Upvotes: 2

Views: 1398

Answers (2)

iain
iain

Reputation: 16284

If you've included jquery_ujs.js into your application, the CSRF token will automatically be added to AJAX requests. You can see that here.

This also has nothing to do with your DoubleRenderError btw. That's your incorrect use of respond_with.

Edit:

Don't disable CSRF protection. Just don't.

You don't need to add the token yourself, everything should go automatically.

The reason why your action causes an error is because of respond_with. If you're only responding to json requests, this is how it should look:

# POST /notes.json
def create
  @note = current_user.notes.new(params[:note])

  if @note.save
    render json: @note, status: :created, location: @note
  else
    render json: @note.errors, status: :unprocessable_entity
  end
end

But, because Rails already knows this pattern (it's a convention), you can shorten it to:

respond_to :json #, :html, :xml (optionally)

def create
  @note = current_user.notes.create(params[:note])
  respond_with @note
end

More details about respond_with are here.

Upvotes: 2

Frederick Cheung
Frederick Cheung

Reputation: 84172

The behaviour triggered by a missing or invalid csrf token changed about a year or so ago. Rather than raising an exception, the new behaviour is to reset the session, so the request is processed as if the user was not logged in.

You can control this behaviour by defining handle_unverified_request and implementing the desired behaviour there.

Upvotes: 0

Related Questions