Ruby version 2.2.4, Rails version 5.0.0.1.
I'm getting stuck at a part of a tutorial where you test login with curl. I get an error
ArgumentError (Before process_action callback: verify_authenticity_token has not been defined).
I used this code in sessions_controller:
skip_before_action :verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }
Does somebody know the answer?
Check if your ApplicationController has a call to protect_from_forgery as follows:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Essentially calling protect_from_forgery adds verify_authenticity_token to the before_filter list, which you can skip in the other controllers.
Refer to protect_from_forgery documentation for more info.
After upgrading to Rails 5, I hit this error. I discovered that if skip_before_action is called multiple times with the same method name, this exception will be raised.
My fix was to add the parameter raise: false, to the second time skip_before_filter was called.
So it would look like this in the Application Controller:
skip_before_action :your_method_name, raise: false
After upgrading to Rails 5 and deploying to Heroku I hit this error. I could not reproduce it in development.
In the API docs the example is given
class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
end
For me, adding the unless: -> { request.format.json? } like the above fixed my stack bomb. raise: false unfortunately did not.
I fixed it by setting:
config.action_controller.default_protect_from_forgery = true in config/application.rb
Just ran into a similar issue and none of these solutions worked for me.
Background:
I was re-naming models and controllers to keep the code DRY, I reviewed the files and found that I missed re-naming one of the controllers i.e. the class name was updated but not the file name.
Solution:
Update the controller name to match the new class name.
Moral of the story:
Cross to 't's and dot you 'i's.
Related
It's my understanding that protect_from_forgery with: :exception which is the default in Rails, will cause an error if forms are submitted and they don't have the authenticity_token input.
However it seems like this is not the case anymore. This is a Rails 5 app, and I've mostly done Rails 4 in the past, so I wonder if something has changed.
In application_controller.rb I have protect_from_forgery with: :exception
My form is like this (using slim)
form#spreadsheet-form{
action='/submit_spreadsheet'
}
textarea.spreadsheet-input{
name='instructions'
style="width: 200px; height: 200px"
}
br
input.spreadsheet-submit{
type="submit"
value="submit"
}
The main issue in my eyes is why this doesn't raise an error. In the past I've had to include a hidden input with form_authencicity_token as the value.
I believe it is something to do with the Rails 5 changed the protect_from_forgery execution order. From this blog
What
If we generate a brand new Rails application in Rails 4.x then application_controller will look like this.
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Looking it at the code it does not look like protect_from_forgery is a before_action call but in reality that’s what it is. Since protect_from_forgery is a before_action call it should follow the order of how other before_action are executed. But this one is special in the sense that protect_from_forgery is executed first in the series of before_action no matter where protect_from_forgery is mentioned. Let’s see an example.
class ApplicationController < ActionController::Base
before_action :load_user
protect_from_forgery with: :exception
end
In the above case even though protect_from_forgery call is made after load_user, the protection execution happens first. And we can’t do anything about it. We can’t pass any option to stop Rails from doing this.
Rails 5 changes this behavior by introducing a boolean option called prepend. Default value of this option is false. What it means is, now protect_from_forgery gets executed in order of call. Of course, this can be overridden by passing prepend: true as shown below and now protection call will happen first just like Rails 4.x.
class ApplicationController < ActionController::Base
before_action :load_user
protect_from_forgery with: :exception, prepend: true
end
Why
There isn’t any real advantage in forcing protect_from_forgery to be the first filter in the chain of filters to be executed. On the flip side, there are cases where output of other before_action should decide the execution of protect_from_forgery. Let’s see an example.
class ApplicationController < ActionController::Base
before_action :authenticate
protect_from_forgery unless: -> { #authenticated_by.oauth? }
private
def authenticate
if oauth_request?
# authenticate with oauth
#authenticated_by = 'oauth'.inquiry
else
# authenticate with cookies
#authenticated_by = 'cookie'.inquiry
end
end
end
Above code would fail in Rails 4.x, as protect_from_forgery, though called after :authenticate, actually gets executed before it. Due to which we would not have #authenticated_by set properly.
Whereas in Rails 5, protect_from_forgery gets executed after :authenticate and gets skipped if authentication is oauth
Is the authenticity token present on your form when you inspect it? Which point is protect_from_forgery inserted in your application?
Rails 4=>5 changed the default behaviour to be inserted wherever in the chain it is called, opposed to the previous where it was called first. If you want it to be called before any other action, try using the prepend: true flag.
I am working on a Rails 5 api project which is used by mobile client with gem devise_token_auth for authorization.
I am clear about what the warning means.
1st Question: CSRF protect should be turned OFF for api(JSON/XML)respond, correct?
I searched some on web it seems CSRF just happens on web application with cookie. But i read this from rails api document:
It's important to remember that XML or JSON requests are also affected >and if you're building an API you should change forgery protection >method in ApplicationController (by default: :exception):
class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
end
So i still get the warning by adding like this:
class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
include DeviseTokenAuth::Concerns::SetUserByToken
end
2nd Question: If API doesn't need CSRF protection, why
protect_from_forgery unless: -> { request.format.json? }
doesn't work?
Not sure if i understood something wrong. Thank you!
the code should be:
protect_from_forgery with: :null_session, if: ->{request.format.json?}
You might have to use null_session for API, it provides an empty session during request but doesn't reset it completely. Used as default if :with option is not specified.
I want to build a rails app with two different protect_from_forgery strategies: one for the web application, and one for the API.
In my application controller I have this line of code: protect_from_forgery with: :exception in order to prevent CSRF attacks, it works just fine.
In my API namespace, I created an api_controller that inherits from my application controller, and that is the parent class of all the other controllers in the API namespace, and I changed the code above with: protect_from_forgery with: :null_session.
Sadly, I have an error when trying to make POST request: "Can't verify CSRF token authenticity".
I don't want to skip the verify_authenticity_token method in my API controllers, I just want to have two distinct strategies in my app, so how do I override the protect_from_forgery strategy defined in my application controller ?
Edit: Ok, so I eventually did what I did not want to do in the first place: change the inheritance of my api_controller: it now inherits from ActionController::Base, and no longer from my application controller. It does work now but:
It does not answer my question i.e. overriding the protect_from_forgery strategy.
It is not DRY as I have to copy/past what was previously in my application_controller.
So if anyone has a real way to overwrite this method, I'd appreciate it.
What if you leave the protect_from_forgery with: :exception in the application controller but then you put the following in your API controller?
skip_before_action :protect_from_forgery
protect_from_forgery with: :null_session
That way, you still get the standard CSRF attack protection for all controllers in your web application but you also get the null session behavior for your API methods.
I am running an application with a similar structure - Web App + API. I solved the CSRF problem like this:
Apply protect_from_forgery only for non API requests
My API endpoint is api.example.com, so I used subdomain constraint to distinguish API and web app requests
Code:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, if: :isWebRequest?
def isWebRequest?
request.subdomains[-1] != 'api'
end
end
Late to the party, but something like this can be done:
class YourCustomStrategy
def initialize(controller)
end
def handle_request
end
end
And in your ApplicationController or where you want:
class ApplicationController < ActionController::Base
protect_from_forgery with: YourCustomStrategy
end
I'm trying to write an Ember application in Rails 4, and have decided to go with rails-api for the api controllers, while keeping the application controller intact for a few pages that aren't part of the single-page app. To put it in more concrete terms, here are my controllers:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
end
app/controllers/sample_controller.rb:
class SampleController < ApplicationController
# my methods
end
app/controllers/api/v1/api_controller.rb:
class Api::V1::ApiController < ActionController::Api
include ActionController::MimeResponds
end
app/controllers/api/v1/sample_controller.rb:
module Api::V1
class SampleController < ApiController
respond_to :json
# my methods
end
end
My application.html.slim contains the following line:
== render partial: "flash_msgs" unless flash.blank?
The inclusion of which results in the following error:
undefined method 'flash' for #< ActionDispatch::Request:0x007f99f41d8720 >
Per discussion on this thread, it seems that the culprit could be rails-api, but I'm not entirely convinced given the inheritance I've set up. Any suggestions?
Not sure but maybe you need to include the ActionDispatch::Flash middleware to support the flash. Using:
config.middleware.use ActionDispatch::Flash
The docs says:
ActionDispatch::Flash: Supports the flash mechanism in
ActionController.
I hope it helps
See: https://github.com/plataformatec/devise/issues/2775
Inside devise.rb change
config.navigational_formats = ['*/*', :html]
to:
config.navigational_formats = [:json]
or just [ ]
If you're like me and creating an API on top of an existing application, you can add this to your config/application.rb file:
config.api_only = false
well, in my case my API mode rails app I had this code in one of my controller:
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
due to which handle_unverified_request was getting called that has this small piece of code request.flash = nil which was raising Undefined method 'flash' for ActionDispatch::Request for me.
Dealt with it by replacing protect_from_forgery with skip_before_action :verify_authenticity_token
There's a gem, which appends a before_filter to a Rails app:
class Railtie < Rails::Railtie
initializer "..." do
ActiveSupport.on_load(:action_controller) do
ActionController::Base.send(:include, Filter)
...
module Filter
extend ActiveSupport::Concern
included do
append_before_filter :set_locale
end
def set_locale
....
And here's some controller in the app:
class DesktopsController < ApplicationController
before_filter :set_language_in_session
Now the problem with this is that the before_filter from the gem is being put in the filter chain before the before_filter from the DesktopsController:
DesktopsController._process_action_callbacks.select { |c| c.kind == :before }.collect { |filter| filter.filter }
=> [
[0] :set_locale,
[1] :set_language_in_session
]
How can I make the before_filter from the gem (set_locale) be put after all other filters? The secret probably lies in this line:
ActiveSupport.on_load(:action_controller) do
But I've tried different libraries without any luck...
Btw. Here's the full code of the gem. Ruby 1.9.2, Rails 3.0.5.
Adapting the gem will never work. The gem is initialised as one of the first things in the rails process, so before any actual controller is ever launched. So this means, that whatever you do, most likely the filter from the gem will still remain the first gem.
So the only solutions I see are:
using prepend_before_filter in your own controllers for those actions that need to come before the set_locale.
if you need explicit control, use the skip_before_filter and explicit before_filter.
In a rails 2 application, we devised a hack to make sure that some filter was run as last filter, by overriding the perform_action_without_filters (see Is there a way to force a before_filter to always execute last?) but we removed that when upgrading to rails 3.
My answer is based on nickgrim's answer, which I also found quite clever. The block passed to included gets class evaled (class_eval) when the module is included. You can make set_locale_filter a class method by defining it in the included block (among other ways).
module Filter
extend ActiveSupport::Concern
included do
append_before_filter :set_locale_filter
def self.set_locale_filter
append_before_filter :set_locale
end
end
def set_locale
#
end
end
Somebody, please correct me if I'm mistaken. It's late here :)
Try this:
class DesktopsController < ApplicationController
skip_before_filter :set_locale
before_filter :set_language_in_session
before_filter :set_locale
end
Reference
skip_before_filter doc
I haven't tried it, but you might check out prepend_before_filter, which claims to bump the provided filter to the top of the list (and ultimately before :set_locale).
class DesktopsController < ApplicationController
prepend_before_filter :set_language_in_session
end
Docs: http://apidock.com/rails/ActionController/Filters/ClassMethods/prepend_before_filter
Another thing to try is using an around_filter for :set_language_in_session (even though you're not doing anything after the action). Around filters happen before before_filters, so it could be a good way to force it to happen first.
The gem itself is on Github so you're able to fork it and make the code changes, then have your code pull your version of the gem instead. Is this an option? If so, you could just comment out the before_filer in the gem and call it yourself in your ApplicationController:
module Filter
extend ActiveSupport::Concern
included do
# append_before_filter :set_locale
end
def set_locale
....
Now just call it yourself after all of your other filters:
class ApplicationController < ActionController::Base
before_filter :do_this, :do_that, :set_locale
...
end
Whatta ya think?
Not tried this at all, but: could you have the gem's before_filter run append_before_filter? Something like:
module Filter
...
included do
append_before_filter :append_set_locale_filter
end
def append_set_locale_filter
append_before_filter :set_locale
end
def set_locale
...
end
...
end
Personally, I'd probably write a piece of middleware that does your operation, because then you don't have to worry about the order the filters happen.
http://guides.rubyonrails.org/rails_on_rack.html