Rails: Warden/Devise - How to capture the url befo

2020-02-09 01:57发布

问题:

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!

回答1:

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


回答2:

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.



回答3:

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


回答4:

You can use request.referer to get the previous URL.



回答5:

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