I need to design a RESTful API for Rails, which will enable login from web browser, smart phone, tablet, etc. When I do login it always require X-CSRF-Token, so everytime I need to use session or cookie info. However the REST api should be stateless, which means shouldn't use cookies. Is there a way to get rid of that? Any suggestion for that?
Here's how I dealt with this in an app that responds with both HTML and JSON. I want the CSRF check except if it's an API call from a trusted source, so
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# has to come after the protect_from_forgery line
skip_before_filter :verify_authenticity_token, :if => :api_request?
# but don't just accept any URL with a .json extension, you need something else to
# ensure your caller is trusted (in this case I test for a valid API key being passed)
before_filter :api_key_valid?, :if => :api_request?
def api_request?
request.format == 'application/json'
end
# ... etc
end
Related
I'm new to Ruby on rails. I need to maintain a project which is a complete web app. Now I need to introduce APIs in it. I've searched and got many tutorials on API and web app separately. But didn't get any of them showing how these things will work together. I'm confused how that authentication will work for both.
Here is the application_controller.rb:
class ApplicationController < ActionController::Base
helper_method :sort_column, :sort_direction
protect_from_forgery
before_filter :authenticate_user!
# before_filter :authenticate # HTTP AUTH DISABLED
rescue_from CanCan::AccessDenied do |exception|
render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false
## to avoid deprecation warnings with Rails 3.2.x (and incidentally using Ruby 1.9.3 hash syntax)
## this render call should be:
# render file: "#{Rails.root}/public/403", formats: [:html], status: 403, layout: false
end
def user_for_paper_trail
if user_signed_in?
current_user.full_name
end
end
def info_for_paper_trail
if user_signed_in?
{ :user_id => current_user.id }
end
end
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "admin" && password == "123"
end
end
end
I need to know how to authenticate APIs? If I use JWT then how to override sign_in methods and do all that stuff separately for APIs and that also look overhead to me because authentication is already there.
Moreover it would be helpful if I get to know how to involve API functions in controller? Like I've user controller and all the methods for that for web app. Now I need the same methods for API. So I need to make new controller for API or that controller can be used?
There are many questions here so I'll try to give a big picture answer:
In general, other controllers inherit from ApplicationController which (in your case) runs a before_filter. The filter can redirect or render and therefore prevent the execution of the specific route. Since all controllers inherit from ApplicationController, the filter is run before every action of your app (assuming the most common default case).
Presumably, API authentication is supposed to work in a different way than for the app's html frontend (perhaps an api key in a header). It looks like your app is using https://github.com/plataformatec/devise. I'd have a look at it to see if you can just "switch on" a suitable authentication method for your API with it.
I hope this helps.
The solution worked for me is to use friendly token with devise_token_auth for api. And here is my before filter now:
before_filter do
if check_api_request
authenticate_api_request
else
authenticate_user!
end
end
I'm trying to use tele_notify gem:
Tele Notify
This Gem use Webhook, so I set it with Telegram:
https://api.telegram.org/bot<TOKEN>/setWebHook?url=https://<EXAMPLE.COM>/<TOKEN>
{"ok":true,"result":true,"description":"Webhook was set"}
Then in Application Controller:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
#IMPORTANT! THESE TWO LINES MUST COME AFTER protect_from_forgery!
skip_before_filter :verify_authenticity_token, :only => :webhook
include TeleNotify::Controller
#other code...
end
And finally the routes:
#config/routes.rb
Rails.application.routes.draw do
post '/<your token>' => 'application#webhook'
end
Is this code secure? Anyone experienced with this gem?
It is a problem to skip authenticity token?
skip_before_filter :verify_authenticity_token, :only => :webhook
Thank you very much!
Yes, it's quite secure. Intentionally or not, but you seem to be following official recommendations:
If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. https://www.example.com/<token>. Since nobody else knows your bot‘s token, you can be pretty sure it’s us.
As for skipping authenticity token check, it must be done, because telegram servers have no way of knowing the token. (it is precisely the idea behind the token and the check: remote servers, not knowing the token, can't make requests. But here you want them to be able to hit this certain endpoint).
I am using ng-auth-token and devise_token_auth for authentication which is working fine. I am able to login from front end but when i visit an API url directly in browser it doesnt show any current_user. What i want to do is i want to integrate paypal checkout, so when i come back from paypal to my app after user authorization, current_user is nil and also session variable is empty (even if i set some session variable before going to paypal site).
If i add
before_action :authenticate_user!
it gives me
Filter chain halted as :authenticate_user! rendered or redirected
even if i am logged in.
I don't know how can i handle these callback response from other apps.
I found a workaround to this, but still waiting for a proper solution.
# In ApplicationController
def authenticate_current_user
head :unauthorized if get_current_user.nil?
end
def get_current_user
return nil unless cookies[:auth_headers]
auth_headers = JSON.parse cookies[:auth_headers]
expiration_datetime = DateTime.strptime(auth_headers["expiry"], "%s")
current_user = User.find_by(uid: auth_headers["uid"])
if current_user &&
current_user.tokens.has_key?(auth_headers["client"]) &&
expiration_datetime > DateTime.now
#current_user = current_user
end
#current_user
end
and use this in controllers
# In any controllers
before_action :authenticate_current_user
source: https://github.com/lynndylanhurley/devise_token_auth/issues/74
Thanks.
Addon to Ankit's solution (rep too low to comment):
This was failing for me on post requests because Rails was stripping out the cookies due to protect_from_forgery being set:
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
include Pundit
protect_from_forgery with: :null_session # <-- this was the culprit
end
Removing protect_from_forgery entirely "solved" the issue, though I'm not happy with it.
The real issue (on my end, at least) is that ng-token-auth is supposed to be including the token in the header, but is only found in the cookies. My current guess is that either 1) ng-token-auth isn't properly setting its HttpInterceptor, or 2) some other interceptor is messing with it after the fact. (I've seen the ng-file-upload can cause issues, but I'm not using that...)
I have ended up with this code in ApplicationController:
before_action :merge_auth_headers
def merge_auth_headers
if auth_headers = cookies[:auth_headers]
request.headers.merge!(JSON.parse(auth_headers))
end
end
I'm trying to work out security for my AJAX calls. I've got a jQuery post call which deletes a note. From what I've read, it seems that I need to use protect_from_forgery to ensure that the post is coming from a valid user.
This is what I have so far
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
...
index.html
$.post('../delete_note',{id:$('#note_id').val()}, function(data) {
});
note_controller.rb
def delete_note
y params
render :text => "success"
end
At the moment, the post request gets run by Rails, even though I'm not sending any security token with it. What do I need to do secure the call?
I'm using Rails 3.0.1 and devise for user management.
You probably want to ensure the user is signed in, using in your note controller something like:
before_filter :authenticate_member!, :except => [:index]
Additionally, check if the user has the rights to delete the note, for that you want to use a authorization solution like cancan.
I have a form on another website (using a different backend) that I want to be able to POST to my Rails application (on a different domain).
How do I generate a valid authenticity token for the external form so that my Rails app will accept it?
Assuming I can do the answer to the above question--is there anything else special I need to do to make this work? Apart from the authenticity token, the rest of it seems pretty straightforward to me...
Thanks for the help!
You can't generate an autenticity token from outside your Rails app.
What you can do, is to disable the token protection only for this action and use a custom implementation based on a before_filter.
skip_before_filter :verify_authenticity_token, :only => :my_action
before_filter :verify_custom_authenticity_token, :only => :my_action
def verify_custom_authenticity_token
# checks whether the request comes from a trusted source
end
You could just remove the check by adding a filter like:
skip_before_filter :verify_authenticity_token, :only => :action_name