We recently needed to implement a fairly standard user authentication / permission system on a Rails 3 project, with the ability to define these permissions using roles so that it could easily be centralized and expanded on (ie with new roles and permissions down the line). While rolling your own custom code is always an option, there is already a robust collection of gems to do exactly this. This blog post quickly presents how to combine three of these to implement this system.
Devise is a modular user authentication system thcgem ‘cancan’
- Generate the Ability class which will contain your user permissions:
1rails g cancan:ability
- Open up this file and add the user line so that you can tie your abilities to the currently logged-in user:
Ruby12345678910111213# app/models/ability.rbclass Abilityinclude CanCan::Abilitydef initialize(user)# Define abilities for the passed in user here.user ||= User.new # guest user (not logged in)# a signed-in user can do everythingif user_signed_in?can :manage, :allendendend
Your application controller will need to know what to do if a CanCan exception is thrown:
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
Now you can decide which actions require CanCan authorization:
@document = Document.find(params[:id])
# require the ability to read documents
authorize! :read, @document
Adding this manually to every action in every controller is obviously a dumb idea, so you can just use load_and_authorize_resource at the top of your controller to authorize all actions in a RESTful fashion.
At this point you’ll want to actually make this more granular and allow abilities to be based on roles, which brings us to Role Model.
The CanCan documentation provides a very nice explanation of the basic concept of using a bitmask to represent many roles per user, and Role Model is a ready-to-go solution if this approach fits your needs.
- Add the role_model gem to your gemfile and run bundle install.
- Edit your user.rb to make it aware of Role Model. With the existing Devise stuff, it should look more or less similar to this:
Ruby1234567891011121314151617181920212223# app/models/user.rbrequire 'role_model'class User < ActiveRecord::Base# Include default devise modules. Others available are:# :token_authenticatable, :confirmable,# :lockable, :timeoutable and :omniauthabledevise :database_authenticatable, :registerable,:recoverable, :rememberable, :trackable, :validatableinclude RoleModel# Setup accessible (or protected) attributes for your modelattr_accessible :email, :password, :password_confirmation, :remember_me, :roles, :roles_mask# optionally set the integer attribute to store the roles in,# :roles_mask is the defaultroles_attribute :roles_mask# declare the valid roles -- do not change the order if you add more# roles later, always append them at the end!roles :admin, :editor, :guest
We effectively defined 3 roles now, now we need to update our ability.rb model to tie our permissions to them.
# Define abilities for the passed in user here.
user ||= User.new # guest user (not logged in)
# a signed-in user can do everything
if user.has_role? :admin
# an admin can do everything
can :manage, :all
elsif user.has_role? :editor
# an editor can do everything to documents and reports
can :manage, [Document, Report]
# but can only read, create and update charts (ie they cannot
# be destroyed or have any other actions from the charts_controller.rb
can [:read, :create, :update], Chart
# an editor can only view the annual report
can :read, AnnualReport
elsif user.has_role? :guest
can :read, [Document, Report, Chart]
This is a really simple demonstration of what can be done fairly quickly with this stack, and in our application it was extended to leverage user-level custom permissions for specific assets and very fine-tuned abilities with conditional displays of specific view segments.
Sign up for the Phase2 newsletter for exclusive content and news.