I am trying to figure out how to redirect a user to the page they logged in from (or failed to login) using Warden/Devise. I figure there is a session variable somewhere that is either available or could be made available.
E.g.
Scenario 1:
Non-authorized user goes to protected page X;
Redirected to login page;
User logs in;
User redirected to protected page x
Scenario 2:
Non-authorized user wants to take a protected action on page x;
User clicks on login link;
User logs in;
User redirected to page x where the action is now available
Any pointers are appreciated.
Thanks!
There is a devise helper method called after_sign_in_path_for(resource)
(http://rdoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers) , and a session variable called session[:"user.return_to"]
which stores the last url. The after_sign_in_path_for
method needs to return a string, then devise automatically uses this path to redirect the user after login.
In my application controller I have put the following which redirects my users to the home page if the session variable is not set:
def after_sign_in_path_for(resource)
(session[:"user.return_to"].nil?) ? "/" : session[:"user.return_to"].to_s
end
If your using CanCan for authorization you can accomplish this be adding the following. If not, you should be able to adapt the concepts into your current authorization system.
app/controllers/application_controller.rb
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = exception.message
if user_signed_in?
redirect_to root_url
else
# Adds the protected page to the login url but only if the user is not logged in
redirect_to login_path(:next => request.path)
end
end
def after_sign_in_path_for(resource_or_scope)
# if a protected page found, then override the devise after login path
params[:user]["next"] || super
end
app/views/devise/sessions/new.html.erb
<% if params[:next] %>
<%= f.hidden_field :next, :value => params[:next] %>
<% end %>
Instead of using session variables this solution uses params in the URL to keep track of the protected page.
Wow, just realized that devise (3.5.2)
does this all by itself behind the scenes (around Devise::SessionsController#new
action), no additional controller modifications required.
If you need to explicitly store
/get
previous location
, please see my previous answer:
Currently (Fall 2015) there's a sexier way to do that:
Devise::Controllers::StoreLocation#store_location_for
:
# Stores the provided location to redirect the user after signing in.
# Useful in combination with the `stored_location_for` helper.
store_location_for :user, dashboard_path
redirect_to user_omniauth_authorize_path :facebook
Devise::Controllers::StoreLocation#stored_location_for
:
# Returns and delete (if it's navigational format) the url stored in the session for
# the given scope. Useful for giving redirect backs after sign up:
redirect_to stored_location_for(:user) || root_path
The methods handle related session
key and value deletion after reading, all you need is to provide your :resource
key (:user
in the example above) and a path to store (dashboard_path
in the example above). See source for the details.
As for the actual answer it'll be something like that:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :access_denied
# ...
private
def access_denied(exception)
store_location_for :user, request.path
redirect_to user_signed_in? ? root_path : new_user_session_path, alert: exception.message
end
def after_sign_in_path_for(resource)
stored_location_for(:user) || root_path
end
end
You can use request.referer
to get the previous URL
.
here's the best I could come up with. Works perfectly also with facebook authentication. by adding more restrictions to the prepending of urls to the session variable you can remove more and more paths you don't want the user to return too (e.g. callbacks, splash pages, landing pages, etc)
#ApplicationsController
after_filter :store_location
def store_location
session[:previous_urls] ||= []
# store unique urls only
session[:previous_urls].prepend request.fullpath if session[:previous_urls].first != request.fullpath && request.fullpath != "/user" && request.fullpath != "/user/login" && request.fullpath != "/" && request.fullpath != "/user/logout" && request.fullpath != "/user/join" && request.fullpath != "/user/auth/facebook/callback"
# For Rails < 3.2
# session[:previous_urls].unshift request.fullpath if session[:previous_urls].first != request.fullpath
session[:previous_urls].pop if session[:previous_urls].count > 3
end
def after_sign_in_path_for(resource)
@url = session[:previous_urls].reverse.first
if @url != nil
"http://www.google.com" + @url
else
root_path
end
end