before_action hook in case of Ajax request - ruby-on-rails

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

Related

before_action with the same callback but different condition not working

this is my before_action in controller
before_action :redirect_to_home, unless: :logged_in?, only: %i[destroy]
before_action :redirect_to_home, if: :logged_in?, only: %i[new create]
My purpose is redirect to home when call new and create action for authenticated user and destroy for unauthenticated user
this is my redirect_to_home callback
def redirect_to_home
redirect_to root_path
end
this is my logged_in? method
def logged_in?
p 'HELLO FROM LOGGED_IN'
session[:user_id].present?
end
when I ran the destroy test spec nothing printed out to the console but when I swap the line and run the destroy test spec again everything looks fine but new and create test spec are broken.
Do you guys have any ideas?
Thanks
Ref this
Calling the same filter multiple times with different options will not work,
since the last filter definition will overwrite the previous ones.
You can do following
before_action :redirect_to_home, only: %i[new create destroy]
And in controller
def redirect_to_home
if logged_in?
redirect_to root_path
else
redirect_to destroy_path #You have to use actual destroy path here.
end
end
before_action doesn't prevent action to be executed if callback returns false.
You can make another method:
def ensure_user_is_logged_in
unless logged_in?
redirect_to_home
end
Then you can use it before_action like this:
before_action :ensure_user_is_logged_in, only: %i[new, create]
It will redirect to home if the user is not logged in.
You can refer to this for more info:
how to execute an action if the before_action returns false

Devise AJAX user authentication doesn't redirect

I have a link in my application.html.erb file
<%= link_to 'Sprzedaż', sell_path, remote: true %>
In the controller I authenticate user with before_action :authenticate_user!. Below is my authenticate_user! method.
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
Basically it works correctly if the user isn't authorized. If the user has enabled Javascript it shows nice notification, and if the user hasn't enabled Javascript it shows alert and redirect to root_path which is good. The problem is that when the user is signed in and click the link nothing happens. It should redirect to the sell_path.
This is my ItemsController
class ItemsController < ApplicationController
before_action :authenticate_user!
def sell
#user = current_user
#items = JSON.parse(HTTParty.get("http://steamcommunity.com/profiles/#{#user.uid}/inventory/json/730/2?l=polish").body)
end
end
This is my ApplicationController
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 :steam_informations
def steam_informations
#steam = session[:steam]
end
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
end
You are trying to override helpers which defined in runtime. This is not how you must to do it.
In your case I recommend you to define for example authenticate! method like this:
def authenticate!
return true if user_signed_in?
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
This method will do nothing if user signed in and redirect to root page if user not signed in. Just define this method in ApplicationController and then use before_filter :authenticate! hook to execute it.
When before_filter \ before_action method returns false - rails won't execute your action. And if return true or just return - rails will execute action after hooks like authenticate! and render your views. To make it clear I'll show you some examples.
class FooController << ApplicationController
before_filter :dead_hook, only: :index
before_filter :nice_hook, only: :show
def index
# this action will be never executed because dead_hook method returns false.
end
def show
# this action will be executed right after nice_hook method because of 'return true' command in nice_hook method
end
def dead_hook
return false
end
def nice_hook
return true
end
end
Another way to do just like you trying to do - monkey-patch devise helper. You can do it like this:
module Devise
module Controllers
module Helpers
def authenticate_user!
# implement your logic here
end
end
end
end
Here you can check out whats going on in devise helpers:
Github Devise Helpers source code
Just for clarification: there is no difference between before_filter and before_action. Feel free to use any of them. before_action newer but before_filter not deprecated.

Skip user and csrf authentication for JSON POST APIs

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.

Why there is no double-render when using before_action?

I am wondering why there is no double-render when there is a redirect_to or render in before_action. Consider this example:
class SomeController < ApplicationController
before_action :callback
def new
callback2
render 'new'
end
def callback
render 'new'
end
def callback2
render 'new'
end
end
I see that before_action will be useless if it can't redirect but how it is made? If I comment the before_action it will throw exception.
How is before_action implemented to not cause double-render?
See the Rails Guide on controllers :
If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled.

How to obtain action level protection using Authlogic and STI?

Given that it is well-documented how to use before_filter for a single user classification, I'm having trouble getting action-level protection for multiple user types. Let me explain:
I've got something like this...
class ApplicationController < ActionController::Base
class << self
attr_accessor :standard_actions
end
#standard_actions = [:index, :show, :new, :edit, :create, :update, :destroy]
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
redirect_to home_url
return false
end
end
def require_admin
unless current_user and current_user.is_a?(Administrator)
store_location
redirect_to register_url
return false
end
end
end
And in the GuardiansController I want to only allow the standard actions for Administrator but all other actions should require Guardian. So I tried this...
class GuardiansController < ApplicationController
before_filter :require_admin, :only => ApplicationController::standard_actions
before_filter :require_guardian, :except => ApplicationController::standard_actions
...
end
Which ends up doing a recursive redirection. There must be a better way?
OK, this is another case of not looking carefully and missing something. I inadvertently had setup the route to redirect the user in a recursive way. The above solution works just fine when you set the routes properly:
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
# this route (home_url) sent the user to another controller which ran a before_filter sending them back here again.
# redirect_to home_url
# So I changed it to a neutral route and it works great!
redirect_to register_url
return false
end
end

Resources