Reputation: 5998
In a Rails project I am setting up my own API, which I would like to call from the same site with some jQuery calls.
For example, I have a route POST /api/v1/employee
.
I want this API call to be available for users of my site, but also for an external client that is calling this API with (for example) basic authentication.
So I would like to protect my API against CSRF when the call is coming from my own site and when it is from an external client I want it to check for the username and password in the header of the request.
I set up jQuery to add the csrf token specified in the meta tags of my page, to the every request it does to the API.
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
}
});
But now I am not sure where to go from here.
In my controller I added the following line that enabled my client to connect to the API without problems, but then I have no protection.
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
If I set up a before_filter
for this controller it always checks for the username and password, even when a csrf token is available.
before_filter :authentication_check
^ authentication_check is a method I created which does a simple basic authentication check (just to get this going).
I also tried to do the following:
protect_from_forgery with: :exception, :unless => :valid_user?
But this probably doesn't do what I think. valid_user?
is a method that checks the basic authentication and returns the user if correct.
I somehow want to combine these 2 things. If there is no session (so it's and external client connecting) I want to authenticate on username/password and otherwise just use the normal CSRF protection.
UPDATE November 25th 2015
Ok I now have the following solution for POST
requests. Still need to add GET
, etc, but here it is.
In my controller I have this:
protect_from_forgery with: :exception, unless: :valid_apikey
My :valid_apikey
thing method is defined like this:
def valid_apikey
result = authenticate_or_request_with_http_token do |token, options|
!User.find_by_token(token).nil?
end
result == true
end
So, if I am correct, the CSRF checking is skipped when the request supplies a token that is in my database. When the token is not in my database the authenticate_or_request_with_http_token
method returns an Array
(which is different than true
).
Upvotes: 0
Views: 760
Reputation: 5998
Ok, got this solved for I think. I also want my GET requests to check for the CSRF token.
In my controller I do the following:
def verified_request?
if !request.headers["X-API-KEY"].nil?
!ApiClient.find_by_token(request.headers["X-API-KEY"]).nil?
else
!protect_against_forgery? ||
valid_authenticity_token?(session, params[request_forgery_protection_token]) ||
valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
end
end
This overrides the verified_request?
method of the inherited ApplicationController. I can't just call super
, because then my GET
requests will not get checked.
Upvotes: 0
Reputation: 1668
I suggest you dont have to put an exception for your protect_from_forgery
You can simply do this
class ApplicationController < ActionController::Base
protect_from_forgery
def handle_unverified_request
# your logic to handle request coming from outside your domain
# if user is not valid then
raise(ActionController::InvalidAuthenticityToken)
# else allow to bypass
end
This way u can control and also for normal requests from your domain, you dont ve to go through if conditions and hitting db everytime.
Upvotes: 1