Extending restful_authentication

Most websites need to offer users several levels of access. Sometimes it’s as simple as a straight choice between logged in, or logged out - something which both restful_authentication and acts_as_authenticated handle competently.

As a site grows, however, so does the likelihood that further levels of authentication will need to be checked. Now, there’s an entire industry dedicated to solving this problem - but here’s a simple and logical way of utilizing some of restful_authentication’s methods to implement such a system. I haven’t tested this in acts_as_authenticated - but as far as I know it should work fine there too.

Firstly, we need to duplicate restful_authentication’s login_required method, in order to handle a new level of user. As an example, we’ll write a method contributor_required, which will act as a filter to ensure a given set of actions are only available to one of a website’s contributors. (I am assuming the existence of a method or two, but these should be pretty self-explanatory.)

class ApplicationController < ActionController::Base
  include AuthenticatedSystem
  ...

  protected

  def contributor_required
    login_required

    if not current_user.is_contributor?
      store_location
      flash[:notice] = "You must be a contributor to enter here!"
      redirect_to new_user_contributor_path(current_user)   # or, send wherever your app requires
      return false
    else
      return true
    end
  end
end

There are a couple of important things to note here. The first line of the method calls restful_authentication’s own login_required method. This could almost be viewed as a ’super’ method: we are assuming that in order to be a contributor, you must already be registered user - and if you’re not, you will be redirected to the basic login/register pages at this point.

Any deeper levels of user we want to create later - senior_contributor, perhaps - should therefore call contributor_required in place of login_required. This method will then itself ‘work up the chain’ and eventually end up calling login_required. We could construct an arbitrarily-sized ‘tree’ of authentication in this manner - mirroring many real-life authentication needs.

One further plus point is that the check for login_required ensures we can rely on the validity of current_user a little later on.

The other thing to notice is that - if the assumed is_contributor? method returns false - we call restful_authentication’s store_location method. This is a simple method which stores the requested URI in the session, allowing access to it later. It means we should be able to redirect back to the requested page, if we can approve the user’s contributor status.

So, all that is left is to utilize the code in our controller, where contributor_required can be used alongside login_required. Also, the use of the usual redirect_back_or_default method means we should be redirected back here after we’ve registered the user as a contributor, or whatever we need to do.

class ContributionsController
  before_filter :login_required, :only => [ :index, :show ]
  before_filter :contributor_required, :only => [ :new, :create, :edit, :update, :destroy ]

  ...

  def create
    ...
    respond_to do |format|
      format.html { redirect_back_or_default( contributions_path(@contribution) ) }
      ...
    end
  end
end

And voila! Simple, but potentially powerful role-based authentication for Rails.

Bookmark it: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • DZone
  • Reddit
  • StumbleUpon
  • Technorati
  • Facebook

One Comment

  1. Posted July 13, 2008 at 5:02 pm | Permalink

    Great post! I guess this allows you to create custom roles. What about a more general extension to create roles and responsibilities? (Eg. each role has multiple responsibilities, actions they can perform.) It should be possible to add a couple of classes and wire it up fairly quickly…

    Something worth looking into.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*