authcan_easyroller

This is a basic Rails engine utilizing Authlogic, CanCan and Easy Roles for simple Rails 3.x applications that need authentication and authorization.

Basis for Creation

As a Rails developer, I find myself needing an elegant and easy-to-use solution for authentication and authorization in the small web-apps that I create. They generally aren't linked together since they are separate clients with completely different ideas and business models, but they both need the same underlying code to manage their users.

I have used Authlogic for quite a while now and have found it to be very useful and a complete solution for authentication. Adding a dash of functionality here and there is fairly easy and the ability to extend it really drew me in (in fact, I helped develop some of the code behind authlogic_ldap which I never got to finish but still intend to do).

After watching the screencast by Ryan Bates, I decided that CanCan was a great addition to my applications since it was easy to use and cleanly separated the authorization from the model and view logic. The only other piece was an easy way to manage roles soley in code, yet while storing the assignments in the database; and I found this in Easy Roles.

Putting the three together, I now have a complete (albeit simple) authentication and authorization solution that I can easily plugin into existing or new applications (and update in one spot).

Future Enhancements

Before we get into the nitty-gritty of how to use it, I wanted to go ahead and mention the planned enhancements so you can get a feel of where this project will go.

  • Authlogic supports many different types of authentication without you needing to change hardly anything, so I plan on modifying the code to allow for all of the possible authentication schemes. Thus, I definitely plan on integrating OpenID, LDAP, Facebook Connect and OAuth. I may or may not implement PAM, but we'll see.

  • I plan on adding an easy password reset mechanism for users provided that you use an email column.

  • I also plan to add the configuration option to turn on e-mail verification before users are granted access to the system.

Installation/Setup

Developers Note: I have created an example project if you are just looking to play with it, or if you are having trouble getting your setup to work properly. It should have everything necessary to get up and running quickly.

Assuming you already have Rails 3 installed, installation is very simple; just add the gem to your Gemfile and run bundle install!

# Add this to your gemfile
gem "authcan_easyroller", ">= 0.1.3"

Run

bundle install

Next, create a migration for the users:

rails generate migration CreateUsers

Then, copy the following contents into that file making any changes you see fit:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      # Necessary Columns - These are required for AuthcanEasyroller to function properly
      t.string    :email,               :null => false
      t.string    :crypted_password,    :null => false
      t.string    :password_salt,       :null => false
      t.string    :persistence_token,   :null => false
      t.string    :single_access_token, :null => false
      t.string    :perishable_token,    :null => false
      t.integer   :roles_mask,          :null => false, :default => 0

# Magic Columns - You may leave any of the following out if you wish
t.integer   :login_count,         :null => false, :default => 0
t.integer   :failed_login_count,  :null => false, :default => 0
t.datetime  :last_request_at
t.datetime  :current_login_at
t.datetime  :last_login_at
t.string    :current_login_ip
t.string    :last_login_ip

      # Timestamp Columns - You should have these on every database table you create
      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Once saved, migrate your database by running

rake db:migrate

Next, copy the following files to their proper locations (feel free to edit them, these are just some basics to help get you started). The rails.js file at the bottom of the list is the official Rails jQuery file available at github.com/rails/jquery-ujs so make sure to remove the existing rails.js file from public/javascripts.

Developer's Note: To save time, you can use wget or curl (which is installed by default on OS X) to download these files from the command line directly into the proper locations. Basic usage is as follows:

cd my_app_directory/some/path/
wget http://github.com/topherfangio/autcan_easyroller-example/raw/master/some/path/filename.rb

or

curl http://github.com/topherfangio/autcan_easyroller-example/raw/master/some/path/filename.rb > some/path/new_filename.rb
  • ability.rb -> <<APPLICATION>>/app/models/ability.rb

  • application.html.erb -> <<APPLICATION>>/app/view/layouts/application.html.erb

  • main.css -> <<APPLICATION>/public/stylesheets/main.css

  • rails.js -> <<APPLICATION>/public/javascripts/rails.js

Finally, remove public/index.html and add the following to your config/routs.rb file so that your server will load properly.

root :to => 'users#index'

Starting Your Engine

Once you have everything setup correctly, simply run the following to start your application, then visit localhost:3000

rails server

The application realizes that it has no users, and forces you to create one before you can continue to any page. The first user is always created as an administrator and thus has privileges to create new users, give themselves the “developer” role (or any/all roles) and do other adminy things. All users created after this will be given the default role of “user”.

Currently, the available roles are

  1. Developer

  2. Admin

  3. Moderator

  4. User

I'm working on a way to extend this functionality as a configuration option, or perhaps give it an API so that you can create your own roles or modify the existing ones without having to delve into the gem's code. Check back often to see where that stands if it is something you need.

Usage

You can find out more by checking each specific project's documentation, but here is the gist:

A user's abilities are defined in app/models/ability.rb. I generally prefer to specify what each role is allowed to do and then give a user all of the roles that they need instead of saying that an admin can do everything that a moderator can do. This tends to keep the ability model cleaner and your views don't change either. In addition, this allows you to assign roles to a user for special circumstances. For instance, if you are writing a help desk app, you may decide that one particular customer is really superb and should also have status update abilities even though he has the customer role.

You define your abilities in each role's section. For instance, the moderator's role currently looks like so:

# Moderator role abilities
if current_user.is_moderator?
end

If you wanted to let moderators manage users, you would simply call can

# Moderator role abilities
if current_user.is_moderator?
  can :manage, User
end

You can also define your own abilities if they don't tie to a particular object, but you must pass a nil object as the second argument to can? and you must specify all object types when defining the ability. I'll get in touch with the developer to see if this can be a tad bit more streamlined.

# ability.rb

class Ability

  # Include the ability class so you can have some defaults
  include AuthcanEasyroller::Ability

  def initialize(current_user)

    # If you override initialize, make sure to call this
    # method so that you have the defaults setup
    ae_ability_defaults(current_user)

    # Moderator role abilities
    if current_user.is_moderator?
      can :visit_woot_all_day, :all
    end

  end
end

# application.html.erb 

<% if can? :visit_woot_all_day, nil %>
  <%= link_to "Woot", "http://www.woot.com" %>
<% end %>

Once you have the roles and abilities setup, your views can check who has access by simply calling can?

if can? :create, Comment
  ...
end

You can also use cannot?

link_to "Export", export_users_url unless cannot? :create, Export

Using can? and cannot? is the preferred method of checking authorization privileges, however, if you find a rare case that you need to limit access based on the role(s) that a user has, you can always do the following:

if current_user.is_moderator? || current_user.is_admin?
  ...
end

However, the beauty of authcan_easyroller (more specifically the CanCan integration) is that you don't have to. That is the exact purpose of the Ability class! Use it to your advantage and make your life easier.

Special Thanks

I would like to thank the creators of Authlogic, CanCan and Easy Roles for the effort that they put into these plugins. Adding them together was relatively straightforward and easy and I hope that they realize how much time this saves other developers!

Copyright © 2009-2010 Topher Fangio