zerosum dirt(nap)

evolution through a series of accidents

zerosum dirt(nap)

Sidebar Login Box Recipe (restful_auth)

July 13, 2007 by nap · Comments

One of the nice features of Rick Olson’s excellent restful_authentication plugin is the store_location facility. If you set up a :login_required before filter in your controller, the access_denied method will get called if the user isn’t logged in. The requested URL will be stored in the session and the user will be redirected to the login page. After logging in, redirect_back_or_default is called by SessionController#create, which pulls the location out of the session and redirects the user back to where they intended to go in the first place. Very slick.

However, if you’ve built an application which has a login box in the sidebar (I recommend simple_sidebar), restful_auth has no idea where you were coming from before the post to the create action. So you get redirected to the default (usually ‘/’) specified as a parameter to redirect_back_or_default. This isn’t usually what the user expects. They expect to be returned to the page they were looking at before if they log in from the sidebar. Oh noes!

Here’s a little recipe you can use to provide that…

First, a simple change to authenticated_system.rb in your lib/ directory (this file is created by the authenticated generator). Change the store_location method to this:

def store_location(location = request.request_uri)
  session[:return_to] = location
end

Before this change, store_location took 0 parameters and always set the return_to location to the current request URI. Now, we’ve modified it so it can (optionally) take the location to store as a parameter. If this parameter isn’t supplied, it defaults to the old behavior.

Next, change the create method in your session controller so it checks the HTTP referer. Just add one tiny little line to the top of the method:

def create
  check_referer
  ...
end

Now let’s write that check_referer method (it should be protected, not public):

def check_referer
  referer = request.env['HTTP_REFERER'] || ""
  if referer.match(request.domain) &&
    !referer.match(session_url) &&
    !referer.match(login_url)
      store_location(request.env['HTTP_REFERER'])
  end
end

What this little utility method does is check the HTTP referer. If the referer is from the current domain, and isn’t the session#create URL or ‘/login’ (in my application, there’s a login_path named route), we go ahead and store the location of the referer. This will later be plucked out by redirect_back_or_default. Note that you’ll need to regex match to make sure the referer value isn’t anything you don’t want to store. For instance, if the user navigates to the login page directly and logs in from there, you don’t want to redirect back to the login page on success. You probably want to redirect to the default page, which is whatever you’re specifying as the default with redirect_back_or_default.

So that’s it! An easy way to handle redirects from sidebar login blocks, without muddying the already elegantly designed redirects that happen when access is denied.

If you want to write a functional test for this, try something like this:

def test_redirect_to_referer # for login sidebar
  location = url_for(:controller => 'foo', :action => 'index')
  @request.env['HTTP_REFERER'] = location
  post :create, :login => 'quentin', :password => 'test'
  assert_response :redirect
  assert_redirected_to location
end
blog comments powered by Disqus