06 April 2014
Friendly Forwarding With Devise
It’s pretty common to require that a user be logged in to perform certain actions around your application. For example, let’s say you wanted to make sure a user was signed in before being able to see any user’s profile. Assuming you’re using Devise for authentication, your code might look something like this:
class UsersController < ApplicationController before_action :set_user, :only => [:show] before_action :require_login, :only => [:show] def show # ... end private def set_user @user = User.find(params[:id]) end def require_login redirect_to new_user_session_path, :notice => "Please sign in." unless user_signed_in? end end
Here, our before action,
require_login, conditionally redirects to the login page if a request hits the
user#show action and no user is signed in.
Note that we make use of Devise’s handy
user_signed_in? method to make this happen.
But what will the user see after they log in? In other words, if I:
- browse to a restricted page,
- get redirected to login page,
- sign in,
- then where do I go next?
Out of the box, Devise redirects to the
root_url upon successfully signing in a
user. In certain cases, this may make sense from the perspective of the
user. However, in the case outlined above, redirecting to the
the deal our application implicitly struck with the user in step 2.
Here are the same four lines in plain English:
- User: “Hey application, show me this thing.”
- Application: “Hey user, ok, as long as you log in first.”
- User: “Mk, np!” <logs in cheerfully>
- Application: “Here’s the homepage, how can I help you today?”
Absurd, I say. Absurd.
So we need to make our application less rude by giving it a tiny bit of short-term memory, but how can we accomplish this in a stateless protocol like HTTP?
A session (or “user session”) is a mechanism for persisting bits of user-specific data across multiple HTTP request cycles, giving the illusion that an application maintains state, or remembers things about its past interactions with a particular user.
How exactly sessions are implemented is beyond the scope of this post, but for now, we’ll content ourselves with the fact
that we can stick information into a hash-like variable called
session in our
controllers, and then retreive it when handling subsequent requests.
Back to our example. In order to keep track of which page the user was asking
when we told them to sign in, we can jot down a reminder for ourselves in the
private def require_login unless user_signed_in? session[:forward_url] = request.fullpath redirect_to new_user_session_path, :notice => "Please sign in." end end
That way, instead of trying to remember our entire conversation, (which is, well, impossible), we simply check the session for reminders everytime we log a user in.
def after_sign_in_path_for user session[:forward_url] ? session.delete(:forward_url) : super end
Devise calls a method aptly name
after_sign_in_path_for to determine where to send the client after login.
This method is implemented internally in Devise, but we can override it by duck
punching it in our
Our implentation checks for a reminder in the session, deletes and returns that
it exists, and otherwise passes the call up the inheritance tree to Devise’s