Rails 5 devise_token_auth Can't verify CSRF token authenticity - ruby-on-rails

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.

Related

JSON API Rails 6 with Devise - SignUp Problems

When i try to use the sign_up method of Devise, i get an internal server error but, after create the user.
My application.rb:
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session, only: Proc.new { |c| c.request.format.json? }
before_action :configure_permitted_parameters, if: :devise_controller?
respond_to :json
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [ :username ])
end
end
Here the output,
Any ideas? 🤔
I am supplementing this with Doorkeeper, but please do not alter the operation of Devise. I also did not use Warden on my own anywhere on the app.
This issue seems to be the problem you are having:
https://github.com/heartcombo/devise/issues/4603
They suggest clearing the cookies of your browser
this usually happens when you are upgrading a bunch of stuck including
devise in one branch And than you get back to some other branch for
something and you have this newer cookie in your browser. Simple
solution is to clear cookies in browser.
Other answers mention upgrading devise version

Shouldn't protect_from_forgery with: :exception raise an error if I don't include an authenticity token?

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.

Devise and Devise Token Auth

I am trying to make rails web app along with rails API for mobile app. For this purpose I am using Devise along with Devise token auth.
I configured routes as it is written in Devise token auth gem so as I could have routes for regular Devise and Devise auth token.
I have 2 problems:
When I add include DeviseTokenAuth::Concerns::SetUserByToken to application_controller it overwrites Devise authenticate_user! and on web side I am being aunthenticated with token.
Possible solution: I created separet ApiApplicationController from which API controllers inherit.
class ApiApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
protect_from_forgery with: :null_session
end
For each POST request which I do in curl to my API I need to add CSRF token.
Possible solution: I could add to both ApplictionController and ApiApplicationController if: Proc.new { |c| c.request.format == 'application/json' } after protect_from_forgery with: :null_session
I used to get the same problem to yours, my solution which is currently working:
# application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session, if: ->{request.format.json?}
end
# api_application_controller.rb
class ApiApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
before_action :authenticate_user!
end

Rails: What does exception and null_session mean in protect_from_forgery

I'm trying to implement token-based API and saw these snippets by google
However, it's hard to understand the meaning by the literal meaning.
Any direction or basic knowledge about this, Thanks ~~
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, if: Proc.new { |c| c.request.format != 'application/json' }
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
end
Rails's document about null_session is here http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ProtectionMethods/NullSession.html#method-i-handle_unverified_request, if you check the source code of it's handle_unverified_request method:
def handle_unverified_request
request = #controller.request
request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
end
that means if the request dose't get through the verify_authenticity_token ,then rails will not fetch the session by cookie data, but create a new session for this request. And that session is a instance of NullSessionHash, so "null_session".
I had this concern when working on a Rails 6 API only application.
By default, Rails applies Cross-Site Request Forgery (CSRF) protection to all controllers that subclass from ApplicationController using the following line:
protect_from_forgery with: :exception
But are there instances where you’d want to respond differently, or even ignore CSRF protection altogether? The answer, of course, is “yes”.
If your project or a portion of your project uses an alternative method for authentication such as API tokens or any other “stateless” authentication, then you can safely remove the protect_from_forgery line from whatever base class those controllers inherit.
On the other hand, if your project uses stateful authentication and APIs, such as those projects with lots of AJAX requests, it can be advantageous to use :null_session with protect_from_forgery like so:
protect_from_forgery with: :null_session
That means the user won’t be logged in anymore for that action and can’t perform the change (if the action requires a signed-in user). However, after the action, the session values will be back and the session ID will be the same, so the user will be logged in.
Rather than throwing an exception, which your JavaScript may not be able to handle, it instead sets the session value to nil for the duration of the action. By doing this, any authorization or action scoped to the current user will result in errors that your JavaScript can more easily work with.
So, if you're working on a Rails API only application, you can use this to protect against Cross-Site Request Forgery (CSRF) attacks. This suffices for both cases when either of them arises:
class ApplicationController < ActionController::API
include ActionController::RequestForgeryProtection
if Proc.new { |c| c.request.format != 'application/json' }
protect_from_forgery with: :exception
end
if Proc.new { |c| c.request.format == 'application/json' }
protect_from_forgery with: :null_session
end
end
This implements protect_from_forgery with: :exception when your requests are not of the application/json format, and protect_from_forgery with: :null_session when your requests are of the application/json format.
Note: protect_from_forgery is a class method included in ActionController::RequestForgeryProtection, which is why include ActionController::RequestForgeryProtection was included.
Resources:
CSRF Protection and Ruby on Rails
Undefined method protect_from_forgery for Clearance::SessionsController:Class
That's all.
I hope this helps

Override the protect_from_forgery strategy in a controller

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

Resources