February 26, 2008
Rails: Completing Requested Action After Authentication
Within Seekler, we have several actions that are only available to logged-in users. We wanted a general solution that would first redirect the user to login or create an account and then complete the action the user initially requested. The primary motivator for this was non-logged-in users clicking 'create your own list' on a Seekler list. If a user clicked this link, we wanted to allow them to login or register and then have the list creation action take place and return them to their newly created list. We had other cases of site actions, that also required being logged in, so I was hoping to find a simple way to do this across the site.
We use the acts_as_authenticated plugin (I know most people are now using restful_authentication, we haven't switched yet), which relies on before_filters to verify users are logged in before using logged-in-only actions. I ended up just adding one line into that system that makes it simple to return the user to his or her previous action after logging in.
In a controller using acts_as_authenticated, there is normally a before_filter requiring authentication before any actions except the actions available to all visitors
before_filter :login_required, :except => [ :show, :list]
This means that on an action like :create the user will end up calling the login_required method in /lib/authenticated_system.rb. If you add the following line to that method, it will record the information you need to redirect the user back to the action they intended after they login.
def login_required username, passwd = get_auth_data self.current_user ||= User.authenticate(username, passwd) || :false if username && passwd session[:last_action] = request.env['REQUEST_URI'] unless logged_in? && authorized? # add this line! logged_in? && authorized? ? true : access_denied end
At this point, the filter will direct all non-logged-in users to your associated login failure action, in our case /account/login
In the login action, (after doing any other login specific code) you just have to redirect the user if they have :last_action set
def login
# login stuff
if session[:last_action]
redirect_to session[:last_action]
else
redirect_to :controller => 'account', :action => 'index'
end
end
You can add a bunch of other things like ignoring certain actions you don't want to forward the user to, or also return the user to wherever they were before clicking a login link, but this simple version gives the basic idea. Obviously this code is specifically for the acts_as_authenticated plugin, but it should be relatively easy to generalize this same sort of solution to work for any type of authentication that is in use on your Rails project.
If you want to see this in action, just visit a list like Best Charities on Seekler and click the 'create your own list' link in the top right.
Posted by Dan at February 26, 2008 11:21 PMWhat happens if REQUEST_URI is an offsite link? Wouldn't it try to redirect back to that site after logging in?
Posted by: MS at February 27, 2008 11:04 PMMS,
You can filter around that to check and only forward if there are cases like that. Since all of these are relatively uncommon actions that are actions with in our site we haven't noticed any problems like that yet.
That is a good point that might be worth adding a quick filter for. We do some various checks and filtering before forwarding the users, so it would be simple enough to verify that as well.
Posted by: Dan at February 27, 2008 11:58 PMI have made similar code for resuming an action after login and it works well. Instead of getting the REQUEST_URI from the environment I just stored the params in the session. Then did the redirect to the stored params. Either way will probably work well but just wanted to post another option.
Also, when would REQUEST_URI have an offsite link? Are you maybe thinking of HTTP_REFERRER? REQUEST_URI should always be the URI of the current request.
Posted by: Eric Anderson at February 28, 2008 8:06 AMEric
good point about the HTTP_REFERRER, I knew there was a reason I didn't worry about it when I first made the code. Yeahs ince it is the current request even if another site links into you it will always be the location on your sever where they linked to. Storing all the params, would work as well, I just came up with this solution before coming up with that.
Thanks for yout thoughts.
Posted by: Dan at February 28, 2008 9:48 AMCrikey, I gotta read all your old blog posts. Adding request completion after logging in was a tiny todo on my giant list. Thanks for prodding me to add it! Of course, I had to change it a smidge. I used the railsy request.request_uri instead of request.env['REQUEST_URI'].
Thanks!

