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
Related
currently I am using the gem delayed-web in my project. I have multiple user roles, and I don't wanna the users whose roles are sales can access to the page delayed web background interface. I already have a method to check for the authentication in my application controller. However, I don't know how to make it work in the route files. Any suggestion would be appreciated.
Updated: I am not using Devise gem. I roll my own authentication.
application_controller.rb:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate
before_action :check_pricer_role, except: [:export_for_customer]
helper_method :check_pricer_role
def check_pricer_role
unless current_user && (current_user.pricer? || current_user.admin?)
redirect_to errors_not_found_path
end
end
end
routes.rb:
Rails.application.routes.draw do
# How to apply the defined authentication here?
mount Delayed::Web::Engine, at: '/jobs'
end
Ok, I've solved the problem. It turns out that I can find the current user based on his authentication token which is saved inside of the Request object(again, I don't use any gems for authentication, I roll my own one, but I don't think that is the problem here anyway).
This is the complete solution, in case somebody way run into the same difficulty:
class AuthConstraint
def matches?(request)
current_user ||= User.find_by_auth_token(request.cookie_jar['auth_token']) if request.cookie_jar['auth_token']
current_user.present? && !current_user.sales?
end
end
Rails.application.routes.draw do
mount Delayed::Web::Engine, at: '/jobs', :constraints => AuthConstraint.new
//Other resources ........
end
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
My Rails app uses Devise for authentication. It has a sister iOS app, and users can log in to the iOS app using the same credentials that they use for the web app. So I need some kind of API for authentication.
Lots of similar questions on here point to this tutorial, but it seems to be out-of-date, as the token_authenticatable module has since been removed from Devise and some of the lines throw errors. (I'm using Devise 3.2.2.) I've attempted to roll my own based on that tutorial (and this one), but I'm not 100% confident in it - I feel like there may be something I've misunderstood or missed.
Firstly, following the advice of this gist, I added an authentication_token text attribute to my users table, and the following to user.rb:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Then I have the following controllers:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Note that my application_controller has the line before_filter :authenticate_user!.)
api/sessions_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api/registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
And in config/routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
I'm out of my depth a bit and I'm sure there's something here that my future self will look back on and cringe (there usually is). Some iffy parts:
Firstly, you'll notice that Api::SessionsController inherits from Devise::RegistrationsController whereas Api::RegistrationsController inherits from ApiController (I also have some other controllers such as Api::EventsController < ApiController which deal with more standard REST stuff for my other models and don't have much contact with Devise.) This is a pretty ugly arrangement, but I couldn't figure out another way of getting access the methods I need in Api::RegistrationsController. The tutorial I linked to above has the line include Devise::Controllers::InternalHelpers, but this module seems to have been removed in more recent versions of Devise.
Secondly, I've disabled CSRF protection with the line skip_before_filter :verify_authentication_token. I have my doubts about whether this is a good idea - I see a lot of conflicting or hard to understand advice about whether JSON APIs are vulnerable to CSRF attacks - but adding that line was the only way I could get the damn thing to work.
Thirdly, I want to make sure I understand how authentication works once a user has signed in. Say I have an API call GET /api/friends which returns a list of the current user's friends. As I understand it, the iOS app would have to get the user's authentication_token from the database (which is a fixed value for each user that never changes??), then submit it as a param along with every request, e.g. GET /api/friends?authentication_token=abcdefgh1234, then my Api::FriendsController could do something like User.find_by(authentication_token: params[:authentication_token]) to get the current_user. Is it really this simple, or am I missing something?
So for anyone who's managed to read all the way to the end of this mammoth question, thanks for your time! To summarise:
Is this login system secure? Or is there something I've overlooked or misunderstood, e.g. when it comes to CSRF attacks?
Is my understanding of how to authenticate requests once users are signed in correct? (See "thirdly..." above.)
Is there any way this code can be cleaned up or made nicer? Particularly the ugly design of having one controller inherit from Devise::RegistrationsController and the others from ApiController.
Thanks!
You don't want to disable CSRF, I have read that people think it doesn't apply to JSON APIs for some reason, but this is a misunderstanding. To keep it enabled, you want to make a few changes:
on there server side add a after_filter to your sessions controller:
after_filter :set_csrf_header, only: [:new, :create]
protected
def set_csrf_header
response.headers['X-CSRF-Token'] = form_authenticity_token
end
This will generate a token, put it in your session and copy it in the response header for selected actions.
client side (iOS) you need to make sure two things are in place.
your client needs to scan all server responses for this header and retain it when it is passed along.
... get ahold of response object
// response may be a NSURLResponse object, so convert:
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
// grab token if present, make sure you have a config object to store it in
NSString *token = [[httpResponse allHeaderFields] objectForKey:#"X-CSRF-Token"];
if (token)
[yourConfig setCsrfToken:token];
finally, your client needs to add this token to all 'non GET' requests it sends out:
... get ahold of your request object
if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:#"GET"])
[request setValue:yourConfig.csrfToken forHTTPHeaderField:#"X-CSRF-Token"];
Final piece of the puzzle is to understand that when logging in to devise, two subsequent sessions/csrf tokens are being used. A login flow would look like this:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
Your example seems to mimic the code from the Devise blog - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
As mentioned in that post, you are doing it similar to option 1, which they say is the insecure option. I think the key is that you don't want to simply reset the authentication token every time the user is saved. I think the token should be created explicitly (by some kind of TokenController in the API) and should expire periodically.
You'll notice I say 'I think' since (as far as I can tell) nobody has any more information on this.
The top 10 most common vulnerablites in web applications are documented in the OWASP Top 10. This question mentioned that Cross-Site Request Forgery(CSRF) protection was disabled, and CSRF is on the OWASDP Top 10. In short, CSRF is used by attackers to perform actions as an authenticated user. Disabling CSRF protection will lead to high risk vulnerabilities in an application, and undermines the purpose of having a secure authentication system. Its likely that the CSRF protection was failing, because the client is failing to pass the CSRF synchronization token.
Read the entire OWASP top 10, failing to do so is extremely hazardous. Pay close attention to Broken Authentication and Session Management, also check out the Session Management Cheat Sheet.
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
I am just getting into rails and begining to understand it slowly. Can someone explain or give me ideas on the benefits or when and whys for coding inside the application_controller? What are some usecases. How are you using the application controller for your rails app? I dont want to put too much code in there because from what I understand, this controller gets called for every request. Is this true?
ApplicationController is practically the class which every other controller in you application is going to inherit from (although this is not mandatory in any mean).
I agree with the attitude of not messing it with too much code and keeping it clean and tidy, although there are some cases in which ApplicationController would be a good place to put your code at.
For example: If you are working with multiple locale files and want to set the locale based on the URL requested you'd do this in your ApplicationController:
before_filter :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
This will spare you the headache of setting locale in each controller separately. You do it once, and you have the locale set all over the system.
Same goes for the famous protect_from_forgery which can be found on the default ApplicationController of a new rails app.
Another use case could be rescuing all exception of a certain type in your application:
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
render :text => "404 Not Found", :status => 404
end
In general, if you have a feature that all other controllers would definitely use, ApplicationController might be a good place for it.
I use it for helpers and methods that need to be used in multiple controllers. A good example is Ryan Bates "Super Simple Authentication from Scratch"
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :admin?
protected
def authorize
unless admin?
flash[:error] = 'Sorry, that page is only for admins'
redirect_to :root
false
end
end
def admin?
session[:password] == "password"
end
end
Then you can all a before_filter :authorize or if admin? from anywhere in your app.