Devise Rails API - ruby-on-rails

can someone will explain to me this line of codes?
skip_before_filter :verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }
and should I use it? Why and why not. Thank you.

The verify_authenticity_token is a before_action (a method called before every controller action, known as a before_filter prior to Rails 4) that Rails uses to protect from CSRF attacks.
You can read more about how Rails does this here.
What this line of code is saying is: "if this is a JSON request then skip the CSRF check for this controller".
This is useful for JSON APIs which need to be made available to remote sites which are not on the same domain, and therefore would fail the CSRF check. This is safe, provided you make sure the API is being authenticated properly. However, if your controller is NOT going to be used by an external web application (and you are just doing AJAX stuff on your own site) then don't turn off the verify_authenticity_token check.

Related

CSRF from JS library to Rails

My task is to write a feature in a javascript package that will be embedded in html sites for form validation. Validation will be done through API call to my server.
The question is how to transfer and validate CSRF token from JS to my Rails server.
I've tried doing this:
var token = function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))};
$.ajax({
url: 'http://localhost:3000/csrf-check',
type: 'POST',
beforeSend: token,
data: {
hey: 'hey'
},
success: function(response) {
console.log(response);
}
})
In my ValidatorController:
class ValidatorController < ApplicationController
protect_from_forgery
def csrf_check
if session[:_csrf_token]
render json: :ok
else
render json: :fail
end
end
end
How can I send CSRF token and validate it on Rails server?
First of all: I don't really understand why you'd want to do such a thing – see max' comment.
Either way, if you want to do some kind of manual check for the authenticity token, you need to disable Rails' own facilities for this particular endpoint and then use the logic behind it in your endpoint. While I haven't tested it, I'd assume something like this should work:
class CsrfTokenChecksController < ApplicationController
skip_forgery_protection
def show
head any_authenticity_token_valid? ? :ok : :bad_request
end
end
This uses the any_authenticity_token_valid? method (see https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-any_authenticity_token_valid-3F) which is used internally by Rails as part of its forgery protection filter.
Note: This method exists since Rails 5. If you're using Rails 4, you'd have to go with valid_authenticity_token?(session, request.headers['X-CSRF-Token']) instead. For Rails 3, it would be form_authenticity_token == request.headers['X-CSRF-Token'].
I would consider if CSRF protection is really even applicable in your case.
Rails CSRF protection serves to guard against cross site reference forgery where another page pretends to be your page by looking visually similar and fooling the user into performing a POST request to for example steal the cookies for a replay attack.
It only actually works in the case where Rails is actually rendering the HTML as it just gives a guarantee that the the request originated from the same server that rendered the form.
If you're making something cross site on purpose it just won't work or even fill any purpose. There are other mechanisms like API keys that can be used instead to verify that the request is legit.

How to handle authentication failures on ajax calls?

My rails controller has two types of handlers, one type is conventional response with a web page, another is designed to respond to $http get requests from Angular, and returns json to be processed by the pages javascript code.
I use devise, and I this code at the top of my application controller
protect_from_forgery
before_action :authenticate_user!
after_action :set_csrf_cookie
The problem is that when for example, the login goes stale, I think authenticate_user is returning my "unauthorized" web page to the caller, rather than the json that would inform the caller that the current user is no longer authorized, and then I could handle the condition on the client side properly.
Any thoughts on efficient way to do this, withoug having to take out authenticate_user! from the application controller.
Most of my controllers have handlers for about 15 routes, about 50/50 which of them are designed to return json to ajax calls, and the others return web pages. I like the security that authenticate_user! in the application controller provides, and am hesitant to remove it and instead have to have different code to handle security in each of my methods.
Thanks.
To understand how this works you really got to get into Warden (which devise is built on top of) and Rack. What before_action :authenticate_user! does is call Warden.authenticate! and asks it to identify a user. Warden identifies users by using strategies. A strategy can be just using session[:user_id] to find a user from the database (which what happens 99% of the time in Devise) or something more novel like HTTP Basic Auth.
If all the available strategies fail then the failure app is called. This is a Rack application. In Devise this is just a basic Rails controller (Rails controllers are Rack compliant applications) that usually returns a redirect. If you are running Rails in the development environment you may get a HTML response though as the error handler that shows you those friendly little exception pages kicks in.
You can customize the response by providing your own failure application:
class CustomAuthFailure < Devise::FailureApp
def respond
self.status = 401
self.content_type = 'json'
self.response_body = {"errors" => ["Invalid login credentials"]}.to_json
end
end
# config/initializers/devise.rb
config.warden do |manager|
manager.failure_app = CustomAuthFailure
end

authorized uses only error in api call - devise_token_auth

I followed the document here which is an abstract from official document and tried to make simple API calls, everything worked fine but none got through "authorized users only" error when making REST calls.
http://localhost:3000/api/v1/auth/?email=bala223344#gmail.com&password=object123&password_confirmation=object123
It is mentioned here that the headers should be sent, but i understand those are for subsquent requests
ah found the answer finally, i checked the log and it said
Can't verify CSRF token authenticity
and i went ahead and changed this ApplicationController
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }

Force Rails Heroku App from subdomain.herokuapp.com to apex domain?

What is the proper way to send a subdomain.herokuapp.com to the apex domain of the application? This is to avoid multiple domain names with the same content.
https://github.com/tylerhunt/rack-canonical-host seems to be the perfect choice for this. Leaving it here for anyone else who has the same question.
Quoting from https://devcenter.heroku.com/articles/custom-domains
The domain myapp.herokuapp.com will always remain active, even if
you’ve set up a custom domain. If you want users to use the custom
domain exclusively, you should send HTTP status 301 Moved Permanently
to tell web browsers to use the custom domain. The Host HTTP request
header field will show which domain the user is trying to access; send
a redirect if that field is myapp.herokuapp.com.
You can redirect requests to the "subdomain.herokuapp.com" using a before filter in ApplicationController or using a constraint in rails routing.
For a comprehensive answer with some bit of extensibility, in totality it looks something like this;
class ApplicationController < ActionController::Base
before_filter :redirect_to_example if Rails.env.production?
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
# Redirect to the appropriate domain i.e. example.com
def redirect_to_example
domain_to_redirect_to = 'example.com'
domain_exceptions = ['example.com', 'www.example.com']
should_redirect = !(domain_exceptions.include? request.host)
new_url = "#{request.protocol}#{domain_to_redirect_to}#{request.fullpath}"
redirect_to new_url, status: :moved_permanently if should_redirect
end
end
This will redirect everything to domain_to_redirect_to except what's in domain_exceptions.

Rails: json response from secure action

Originally I had quite usual ajax form with json response:
def create
# created logic omitted as most likely irrelevant
render :json => {:success => true} #over simplified JSON for debug purposes
end
So far so good, works as expected. I've added security on the create action via ssl_requirement gem:
class RegistrationsController < Devise::RegistrationsController
ssl_required :create
# rest of the code omitted, 'create' action as above
end
All of a sudden I get the following in my form response (observing in HttpFox):
Error loading content (NS_ERROR_DOCUMENT_NOT_CACHED)
The create action runs as expected (enforces HTTPS, creates an object but... fails in the browser. To be specific, fails in Firefox (works on chrome). Any clues and ideas will be greatly appreciated.
Regards,
I'm not certain, but I believe your problem has to do with cross-site AJAX requests.
The fact that you are using a different protocol is making firefox believe you are making a cross-site request. Chrome, I believe, is less strict with this restriction when on local. Try visiting the site itself over https and see if the AJAX request goes through.

Resources