Is there a way to skip require_user for JSON APIs.
I have a web application that sends JSON POST requests to my rails app and expects a JSON response. However, each request is being redirected to the login page as (I assume) it is not being registered as having a session.
So far this is what I have:
memo_main_tester_controller.rb
class MemoMainTesterController < ApplicationController
before_action :require_user, unless: :json_request?
...
This is where API methods are
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
skip_before_action :verify_authenticity_token, if: :json_request?
helper_method :current_user
...
def json_request?
request.format.symbol == :json
end
private
def current_user
User.where(id: session[:user_id]).first
end
def require_user
the_user = current_user
unless the_user
redirect_to login_path, notice: 'You must be logged in to view that page.'
end
end
I got the method json_request? from a search through SO, but I don't think it's working.
When I send a POST request to the memo_main_tester_controller the AJAX request hits a 302 and I am sent the login page with a 200. How do I stop this and get my expected JSON response?
You app should be working on this stage. json_request? looks fine too. Thought there is a more generic way to rewrite it:
def json_request?
request.format.json?
end
I think issue should be your request URL you call from your AJAX call is not ending with .json.
Related
I'm trying to set in cookies target_path that non authorized user tried to reach and after authorization redirect him to the target. Everything works fine and good, but then I tried to set as target edit_test_path or create_test_path and other methods with POST/PATCH/PUT requests and seems that no cookies are being set. What can be the case?
application.rb - I'm setting cookies here. authenticate_user! calling in almost every controller before_actions
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
helper_method :current_user,
:logged_in?
private
def authenticate_user!
unless current_user
cookies[:target_path] = request.path_info
redirect_to login_path, alert: 'Verify Email or Password'
end
end
def current_user
#current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
def logged_in?
current_user.present?
end
end
sessions_controller.rb - I'm trying to redirect to the target from cookies here
class SessionsController < ApplicationController
def new; end
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id
cookies[:target_path] ? (redirect_to cookies[:target_path]) : (redirect_to root_path) # With verb POST cookies don't work
else
flash.now[:alert] = 'Verify Email or Password'
render :new
end
end
def exit
session[:user_id] = nil
redirect_to login_path
end
end
I don't think, you can do this with POST/PUT/PATCH requests. When you are doing redirect_to, rails sends 302 Found reponse with location specified in parameter of redirect_to in your case cookies[:target_path] or root_path.
Browser then understends it should do redirect and sends GET request to URL specified in location header. You cannot nor should try to tell it to do POST/PUT/PATCH requests - these types of requests usually also require some kind of data (eg submitted form) that goes along with the request. You lost all of these data during redirect to login page anyway.
What I am trying to say - use these redirects only for GET requests. It will not work for POST/PUT/PATCH.
I am using the idiom described in https://guides.rubyonrails.org/v2.3/action_controller_overview.html#other-ways-to-use-filters
# /app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter do |controller|
redirect_to new_login_url unless controller.send(:logged_in?)
end
end
Now if the signing in process is successful, how
can I examine if it was, and b)
how can I re-redirect the user to the requested controller action?
How do I do this login-process via AJAX and JSON ?
EDIT: Also I get the following Error Message
uninitialized constant ApplicationController::LoginFilter
When I use the more elaborate solution suggested in 6.2 Other Ways to Use Filters instead of the one above such that my Controller looks like this
# /app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_return_path, LoginFilter
def set_return_path
return if devise_controller?
session['user_return_to'] = request.url unless current_user
end
class LoginFilter
def self.filter(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "You must be logged in"
controller.redirect_to controller.new_login_url
end
end
end
end
Thanks
von Spotz
You can add a before_action in the application_controller.rb where you save the requested page url:
class ApplicationController < ActionController::Base
before_action :set_return_path
def set_return_path
return if devise_controller?
session['user_return_to'] = request.url unless current_user
end
end
And then redirect the user to this url after the successful sign-in:
class SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
return root_url if session['user_return_to'].blank?
session['user_return_to']
end
end
Some of my controllers have before_action hooks in order to make sure that the model instance belong to the right user, or that a user is authenticated
before_action :authenticate_user!
before_action :check_owner
private
def check_owner
Unless current_user.id == Myobject.find(params[:id]).user.id
redirect_to root_path
end
Now I am wondering if I do an Ajax request (remote: true argument to a form) would the before_action hooks pass in case no User is authenticated for example ? At least I guess the redirections would fail..
If the hooks do the job, then I am happy. But maybe there is a better way to do this or get the redirections work.
The before_action would still halt the request in the case of remote: true, however as you mentioned, the redirect as you have it above will only work for requests with HTML format. The best way to handle the redirect for a JS request is by adding your own method in ApplicationController to handle all desired request formats:
class ApplicationController < ActionController::Base
...
def all_formats_redirect_to(path)
respond_to do |format|
format.html { redirect_to path }
format.js { render js: "window.location = #{path.to_json}" }
# you can add how you want to handle redirect for other formats if you use them
end
end
...
end
And then instead of using redirect_to in your before_actions (or even in actions that handle multiple formats), you can use all_formats_redirect_to:
before_action :authenticate_user!
before_action :check_owner
private
def check_owner
unless current_user.id == Myobject.find(params[:id).user_id
all_formats_redirect_to(root_path)
false
end
end
Problems with Devise!
I've been messing with it all weekend to no avail, maybe one of you guys know what's up.
I can successfully create and sign in with a user returning a 201 and the all the proper stuff (user_token and email_token), however any requests after the sign_in are rejected with a 401.
#app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include ActionController::HttpAuthentication::Token::ControllerMethods
include AuthenticatorHelper
# # Prevent CSRF attacks by raising an exception.
# # For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :exception
helper_method :after_actions
# Use Devise to authenticate user before every action
before_filter :authenticate_user!
private
def id_filters
p = params.permit(ids:[])
return nil if p.blank?
p[:ids].map {|x| x.to_i}
end
end
The code enters the authenticate user action and then stops working. The helper I have included is this:
# Authenticates users by their authentication token and email if present.
# https://github.com/simplabs/ember-simple-auth/tree/master/packages/ember-simple-auth-devise#user-content-server-side-setup
module AuthenticatorHelper
def self.included(base)
unless base.instance_of? Module
base.prepend_before_filter :authenticate_user_from_token!
end
end
def authenticate_user_from_token!
authenticate_with_http_token do |token, options|
user_email = options[:user_email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
end
The Ember Application that sits above my Rails App is sending over the proper headers each time: Token user_token="FEHR4ZpTGzsUSFvyzyKv", user_email="taylor#yolo.com" and was not changed when I updated Rails.
Another weird thing I noticed when I entered the debugger is the before_ filter :authenticate_user is executed before the sign_in my SessionsController. And here is the code for that Controller
#app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def create
respond_to do |format|
format.html { super }
format.json do
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
data = {
user_token: self.resource.authentication_token,
user_email: self.resource.email
}
render json: data, status: 201
end
end
end
end
Does anyone have any ideas as to what changes to Rails could have caused this problem? Any hunches? Could something else have caused the issue? Any guesses would be greatly appreciated! Thanks in advance for any help on the issue, and let me know if I you want to see any more code!
I have defined my own method authorize_user in one of my controllers, as:
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
I want to rescue from the CanCan exception (or any other exception for that matter). I have used Rolify in my app. How do I rescue and redirect to the root_url of my app with a custom message?
I have tried the following options, but none of them worked:
Try 1:
rescue CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
Error in this case: syntax error, unexpected keyword_do, expecting '('
Try 2:
rescue CanCan::AccessDenied
redirect_to root_url, :alert => "Unauthorized Access"
Error in this case: Render and/or redirect were called multiple times in this action
How do I solve this issue?
This is my controller code:
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
after_action :authorize_user
# Some basic CRUD actions
private
def set_cab
#cab = Cab.find(params[:id])
#operator = Operator.find(params[:operator_id])
end
def cab_params
params.require(:cab).permit(:category, :number)
end
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
I think you could try the rescue_from method.
For example, your ApplicationController, would look like this:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to root_url, alert: "Unauthorized Access"
end
end
Since the question was updated with more code, here is additional information:
Some suggestions:
Make the :authorize_user a before_action as well. That way you don't need to worry about code running in the action even when the user was not allowed to do stuff.
You might also need to add the same :only option as for the :set_cab since you use the #operator instance variable.
Last, a personal code style preference is that I would have changed the if ! to unless to increase reading flow.
Try redirect_to(...) and return.
Agreeing with Jakob W I would like to point, that authorization (and authentication) MUST be performed only before action. What is the purpose of any authorization and exception raising when DB transaction, reading/writing to filesystem etc have been already done?
And using before_action has no problem with Render and/or redirect were called multiple times in this action - there will be only one redirect - in exception handling before controller method call.
So, I recommend next code (updated Jakob W's sample):
class CabsController < ApplicationController
#...
before_action :authorize_user
private
#...
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to(request.referrer || root_path), alert: "Unauthorized Access"
end
end
Could I recommend another authorization gem? I think this one is flexible and easy to use - pundit (https://github.com/elabs/pundit). Page on github has some useful tips on authorization.