cancan/devise - redirect to requested path - ruby-on-rails

When a user visits a page that they are not authorized to view they get redirected to the login page. After they log in they are redirected to the site root. Is there a quick and easy way to redirect them back to the page they initially asked for?

You could add a hidden field referring_page to your sign_in-form to add the former referrer to that field and route back to there if its existing, like this:
def after_sign_in_path_for(resource)
params[:referring_page] || super
end
It's a manual solution but i prefer it to using complex logic on controller side.

I dont understand what you exactly want to achieve by saying "if a user visits a page that they are not authorized to view they get redirected to the login page. After they log in they are redirected to the site root"
I assume there will be two sets of users one is admin- user and another is non-admin user the way i handle this will be something like this,
Inside application controller
class ApplicationController < ActionController::Base
protect_from_forgery
check_authorization :unless => :devise_controller?
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
protected
def stored_location_for(resource_or_scope)
nil
end
def after_sign_in_path_for(resource_or_scope)
if current_user.admin?
#add your desired path
else
#redirect to your desired path
end
end
end

Related

With Ruby on Rails 5, how can I have Pundit redirect back when not authorized to the same stored_location_for() Devise uses?

I have followed the Devise wiki on how to set up a store_user_location! method to redirect back to the previous page after sign_in/sign_out and would like to use this same method for redirecting with Pundit after a user_not_authorized is triggered but I'm not sure what to supply for the "resource_or_scope". Usually this is something Devise supplies in it's callbacks (after_sign_in_path_for(resource_or_scope)). Is it possible to use this same method with Pundit and what do I supply as the resource_or_scope?
def user_not_authorized
flash[:error] = "You are not authorized to perform this action."
stored_location_for(what_goes_here?)
end
Try to the following below, I usually approach that like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
# Redirects the browser to the page that issued the request (the referrer) if possible,
# otherwise redirects to the root location.
def user_not_authorized
redirect_back(fallback_location: root_path)
end
end
Hope it helps!
I found what I was looking for and the answer to "what_goes_here" is ":user", which I got to from the linked wiki's function:
def store_user_location
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
Unfortunately, this does not work like I was hoping as my before_action :store_user_location!, if: :storable_location? stores the location attempting to be accessed before it is authorized and if the user ends up being not authorized it just redirects back to the same URL they are not authorized for and an infinite redirect loop ensues.
I also tried the redirect_back fallback: root_path option and for whatever reason, it just always takes me back to the root_path, which I do not like from a usability standpoint.
I'm now looking into other options but it looks like it's going to end up just displaying a 401/404 error page when not authorized and the user can just use their browser's back button.

Rails devise: Redirect user after log in to the url he wanted to go

I need to redirect my users to the page they wanted to go, not to the previous one, for usability reasons.
Right now If Im in page B and then want to go to page A that is log in required then user enters his credentials and is redirected to page B instead of page A.
This has become a very serious usability problem in my app.
Imagine you are seeing a product and you click "Buy now", then you need to enter the log in details, right now after you log in you are redirected back to the product instead of the checkout form.
This is how my code looks like:
class ApplicationController < ActionController::Base
after_filter :store_location
def store_location
# store last url as long as it isn't a /users path
session[:previous_url] = request.fullpath unless request.fullpath =~ /\/users/
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
end
end
What you are looking for is a request.referer. When user goes to buy now and is redirected to login screen, buy now url is a referer in this case.
Just copy the below code in your app/controllers/application_controller.rb file
class ApplicationController < ActionController::Base
protected
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
dashboard_index_path
end
end
I hope it'll help you..

Rails3 redirect to previous action upon user authentication? (before_filter & store_location)

I've read almost all the posts related to Rails friendly redirecting, but I can't seem to figure out how to redirect a user to a previous action after authenticating with Devise.
This is what I would like to do.
Unlogged-in user clicks "Vote"
User is directed to login page, because of "before_filter :authenticate_user!" (I've gotten this far)
After user logs in, "after_sign_in_path_for(resource)" redirects user to previous action (the vote).
The vote action is casted, and then is redirected to the original page where user clicked vote button. ('request.referer' does this if the user was already signed in, but because the user had to go through the login page in this case, request.referer doesn't work.)
*note that I want to redirect to the previous "action," not just the "page." In other words, I want the intended action to have already been performed after user logs in.
Here is my code.
class MissionsController < ApplicationController
before_filter :store_location
before_filter :authenticate_user!, :except => [:show, :index]
def vote_for_mission
#mission = Mission.find(params[:id])
if #mission.voted_by?(current_user)
redirect_to request.referer, alert: 'You already voted on this mission.'
else
#mission.increment!(:karma)
#mission.active = true
#mission.real_author.increment!(:userpoints) unless #mission.real_author.blank?
current_user.vote_for(#mission)
redirect_to request.referer, notice: 'Your vote was successfully recorded.'
end
end
end
and in my applications controller,
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
sign_in_url = "http://localhost:3000/users/sign_in"
if (request.referer == sign_in_url)
session[:user_return_to] || env['omniauth.origin'] || request.env['omniauth.origin'] || stored_location_for(resource) || root_path
else
request.referer
end
end
private
def store_location
session[:user_return_to] = request.referer
end
I think my main problem is that the "request.referer" inside the vote_for_mission action is going somewhere unintended when the user is required to log in, because the previous page is the signin page. Somehow, I would like to save the page where user clicks vote as FOO -> save the vote action as BAR -> redirect to signin page -> when user logs in, redirect to BAR -> after performing the BAR action, redirect to FOO.
Thanks in advance for the help!
What I generally do is to have methods within ApplicationController like this:
def remember_location
session[:back_paths] ||= []
unless session[:back_paths].last == request.fullpath
session[:back_paths] << request.fullpath
end
# make sure that the array doesn't bloat too much
session[:back_paths] = session[:back_paths][-10..-1]
end
def back
session[:back_paths] ||= []
session[:back_paths].pop || :back
end
Then, in any action (in your case the login handler), you can just
redirect_to back # notice, there is no symbol
and in every action you would like to be able to jump back to, just call
remember_location
I hope this helps.

Redirect user after log in only if it's on root_path

I have a root_path on my Rails application that is not user-protected i.e. it's a simple portal homepage, with a login form.
After the users log in, I'd like it to go to dashboard_path.
I've done this:
def signed_in_root_path(scope_or_resource)
dashboard_path
end
This apparently should be used when an user signs in, and I don't want it to go to the root_path, while still keeping the user going back to a previous page if it tries to hit a restricted area and it's either timed out or not logged in.
i.e.:
restricted_page -> login -> restricted_page_but_logged_in
I don't want to change this behavior, and that's why I haven't used after_sign_in_path, but want to redirect it if it's on root_path, or any route that doesn't require user authentication.
My problem is that this is not working. After signing in, I'm getting redirected back to root_path, which I believe is because of after_sign_in_path getting triggered before.
Is there any way to do this? Thanks!
Edit: This works the second time I log in, i.e. I go to root_path, log in, gets the flash message stating me that I'm logged in, and enter username and password again on the form on root_path. I successfully get redirected to dashboard_path. Still, not quite the behavior I want.
Just a thought
You can define two root url one for signed in url which will point to dashboard and second for non signed in users which will point to login page
define different root url based on some constraints
in routes.rb
root url for signed in users
constraints(AuthenticatedUser) do
root :to => "dashboard"
end
root url for non signed in users
root :to=>"users/signin"
then create class AuthenticatedUser in lib/authenticated_user.rb
class AuthenticatedUser
def self.matches?(request)
user_signed_in?
end
end
now if user is signed in root_url will point to dashboard else it will point to signin page
Your can also create two roots using(did not tested it yet)
root :to => "dashboard", :constraints => {user_signed_in?}
root :to => "users/signin"
more on constrains
http://edgeguides.rubyonrails.org/routing.html#request-based-constraints
Note
The priority of url is based upon order of creation,
first created -> highest priority resources
It sounds like you're over complicating the issue. If you get into overriding routing variables it just leads to headaches down the line. I would recommend using a before filter to require a login and use the except param or skip that before filter for your landing page if you're using a separate controller. As an example:
class ApplicationController < ActionController::Base
before_filter :require_login, :except => :root
def root
# Homepage
end
protected
def require_login
redirect_to login_path and return unless logged_in?
end
end
(Make sure you have logged_in? defined)
If you are using a separate controller it will look something like this:
class HomepageController < ApplicationController
skip_before_filter :require_login
before_filter :route
protected
def route
redirect_to dashboard_path and return if logged_in?
end
end
Regarding proper routing after a login, that would come down to what you're doing when you're creating your session. Regardless, this setup should catch anyone that's logged in trying to hit the homepage, and route them to your dashboard and anyone trying to hit restricted content (Anything besides root) and route them to the login_path
You can override the after_sign_in_path_for method without losing the desired behavior.
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || dashboard_path
end
I would also put a condition in the root_path action that redirects if current_user exists.
RootController.rb
def index
if current_user
redirect_to dashboard_path
else
super #or whatever
end
end
You could also use a before_filter, if you wanted to add the redirect to many unprotected actions.
I'm using Omniauth and this method has worked well for me. If you're working with a different strategy, I'm sure you could modify it.
After they log in, just redirect them to root_path and it will take them to dashboard_path or whatever other default you set in the routes file below.
Set up your helper and callback methods in the app controller:
# application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authenticate_user!
unless current_user
redirect_to root_url
end
end
end
Put the before_filter in the restricted controller to catch unauthorized people:
# dashboard_controller.rb
class DashboardController < ActionController::Base
before_filter :authenticate_user!
def index
end
# rest of controller here
end
Add these two items to the route. The first one will not be executed if the condition is not met. If it is, then the top one takes precedence and all root_url calls will go to the dashboard
# routes.rb
YourAppName::Application.routes.draw do
root :to => 'dashboard#index', :conditions => lambda{ |req| !req.session["user_id"].blank? }
root :to => 'static_page#index'
# the rest of your routes
end
I think you're solution is more complex than necessary. Why don't you just do something simple like this on the action that the login form is posted to:
def login
// logic to check whether login credentials authorize user to sign in
// say 'user_signed_in?' is boolean to determine whether user has successfully signed in
redirect_to(user_signed_in? ? dashboard_path : root_path)
end
The login form is on every pages (top/sidebar) or you have a login page?
If is on every pages, you can use request.referrer to know where the user came from.
You can control this with a before_filter on your application controller.
I'm not sure whether or not you're using an after_filter or before_filter somewhere for your redirects but you might be able to use a skip_filter in your login controller. Then put in your custom redirect as a filter within that controller.
Skip before_filter in Rails

how to add an interstitial page to prompt a user to enter a password before seeing Page#Show

right now my app has a Pages model. Anyone with the url, /pages/3 can view a page. I want to make makes have the option of public or private.
If public anyone with the URL can view.
If the page if private, only users that enter a password should be able to view the page.
Right now the page is rendered with the Page#Show controller. What's the right way to go about handling this so that when a user tries to access a private page they first need to enter a password correctly and then they can view the page? How would I structure this in the controller?
Thanks
Since you are using cancan:
def show
#page = Page.find(params[:id])
authorize! :read, #page
end
This will raise a CanCan::AccessDenied error (if the user isn't logged in or isn't authorized), which can be caught like so (docs):
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
# Save the requested path in the user's session
session[:return_to] = request.fullpath
# Send the user to the sign in page
redirect_to sign_in_path, :alert => exception.message
end
end
Depending on your authentication system, on a successful sign-in, in your sessions controller you can:
redirect_to session[:return_to] || your_default_after_sign_in_path
# Clear the :return_to value
session[:return_to] = nil
I may be missing some details, but this is the gist of it. Best of luck.
EDIT:
I should attribute the friendly forwarding part of my answer to Michael Hartl and his book.
Looks like you need an authorization solution. I recommend CanCan its pretty flexible. You can probably tweak it to use "Pages" as the resource that you "login" to instead of the standard "users" if you don't already have authentication and/or don't want authentication per user.

Resources