By Robert Huberdeau. Published on December 30, 2010
At this point in the blog tutorial we are able to create articles, comments and tags. What we need now is a way to limit access to the blog application so that not everybody can create articles. The blog application will need an authentication system to authenticate users and require users to be logged in to access various parts of the blog application. In other words, users will be required to login before they can reach the new, update and delete actions. However, limiting access to anyone who creates an account will not suffice. We will also need user roles to determine who can do what, when they can and cannot do it and where. This would require a great deal of work to do from scratch but luckily for us there are two gems that accomplish all of this- Devise and CanCan. Tony Amoyal has written up a nice post on how to get the two gems working together. Between the instructions for both gems and Tony's post we have everything we need to get started.
To start open your .gemfile and add these two lines:
gem "devise", "~> 1.1.3"
gem "cancan"
Then from the command prompt type in:
bundle install
The bundle command in new to rails 3 and will install your gems for you. Now that Devise is installed we need to get it setup. Following the installation instructions from the the github repository run these commands:
rails generate devise:install
rails generate devise user
rake db:migrate
Devise requires us to set up a root path. To do that open /config/routes.rb and near the bottom add this line:
root :to => "articles#index"
This will set up our index action in the articles controller as the root path but in order for it to work we need to delete the public/index.html. Now we can start restricting access to our application. We want to restrict access to the create, edit, update and delete actions. Best practice when restricting access to an application is to restrict access to everything and then list the exceptions. In this case that would be the index and show controller actions. To do this, add this line to the top of the articles controller:
before_filter :authenticate_user!, :except => [:index, :show]
Now it's time to create user roles to limit certain abilities to certain users. For instance, while we may want various people creating new posts, we may want to limit the ability to delete and edit articles to an admin. Or we may want to moderators to monitor the comments. These users should be able to edit and delete comments but not create articles. Following Tony's post we'll be creating a role class and setting up a has and belongs to many association between the the role and user classes. In the command prompt type in:
rails g migration roles
rails g migration user_roles
Open /db/migrate/roles and edit it to look like this:
class Roles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :roles
end
end
Now edit /db/migrate/user_roles to look like this:
class UserRoles < ActiveRecord::Migration
def self.up
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
end
def self.down
drop_table :roles_users
end
end
Now migrate the database:
rake db:migrate
Next we need to set up the relationship in the models. Add this line to the user model:
has_and_belongs_to_many :roles
Now create a role model. To do this simply create a new file in the /model directory and name it role.rb. Edit it to look like this:
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
CanCan uses an ability model to define which users can do what. We will base each ability off of the roles that we create. At this time it would be a good idea to figure out which roles we would like to have. Lets assume that there will be many authors, lots of comments that needs moderation and an admin to keep everything in line. So, we'll need admin, author and moderator roles, but how do we create them? We only created a role model. We're left with two choices- finish the scaffold for the role class or create the roles using the command console. I went with the latter as I have command line access to my production environment. I created the following roles, Admin, Moderator, and Author in this exact order. This will be important latter when we set a default user role.
Once you've created the roles, create a new model ability. Edit the ability model to look like this:
class Ability
include CanCan::Ability
end
The include CanCan:Ability line allows the model to inherit from the CanCan gem. Our admin role will be able to manage all resources, moderators will be allowed to edit and update comments and authors will be allowed to create articles and update their own articles but not other authors. To achieve all this we edit the ability model to look like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role? :Admin
can :manage, :all
can :publish, Article
elsif user.role? :Moderator
can :read, [Article, Comment]
can [:edit, :update], Comment
elsif user.role? :Member
can :read, :all
can :create, [Article, Comment]
can [:edit, :update], Comment
end
end
end
The def initialize user bit is for guest users. We'll need to add these lines to our user model:
def role?(role)
return !!self.roles.find_by_name(role.to_s)
end
And while we have the user model open, lets setup a default role for new users. Lets make new users automatically authors:
before_create :setup_role
private
def setup_role
if self.role_ids.empty?
self.role_ids = [3]
end
end
Now our user class can find roles by name and new users have a role_id of 3, which corresponds to the third role we created- the author role. But before we can create a user, we need to create some roles. Since we've only created a model, we'll need to use the command prompt to create some roles. In the command prompt type in:
rails console
r = Role.new
r.name = "Admin"
r.save
r = Role.new
r.name = "Moderator"
r.save
r = Role.new
r.name = "Member"
r.save
Now we have everything in place- try it out- start up your server and then hit this url, http://localhost:3000/users/sign_up. Create a new user then open the command console. Type in the following:
u = User.first
u.role_ids
u.role_ids = [1]
u.save
u.role_ids
First we found our user and then displayed the role_ids. The default value should have been 3. We then updated the role_ids to 1, which is the value for an admin. We saved it and then displayed it again to make sure the changes saved.
Lets start restricting access to the blog application based on user role. We'll need to restrict access to controller actions, by role, and then hide links for roles without the required permission level to access the controller actions as well. To restrict access to the article controller actions we'll need to add the following to the respective controller actions:
authorize! :edit, @article
authorize! :create, @article
authorize! :update, @article
authorize! :destroy, @article
CanCan also has a method, load_and_authorize_resource, that would make things simpler, but I have never gotten it to work properly. So, for this tutorial anyways, we'll stick to adding authorize! to each controller action that we want to restrict. We'll also need to add the following to the application controller:
rescue_from CanCan::AccessDenied do |exception|
flash[:alert] = exception.message
redirect_to root_url
end
This is explained in the CanCan documentation- it's needed for users that try to access controller actions that they do not have permission to. Now that we've restricted access to the article controller actions, lets hide some links. Open /view/articles/_article and edit it to look like this:
<p>
<b>Title:</b>
<%= article.title %>
</p>
<p>
<b>Body:</b>
<%= article.body %>
</p>
<%= link_to 'Show', article %>
<% if can? :update, article %>
<%= link_to 'Edit', edit_article_path(article) %>
<% end %>
<% if can? :delete, article %>
<%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
There's still plenty left to update- the comments controller as well as the other views, but you should have enough information to do that on your own. If you run into problems, just leave a comment.
Tags: blog tutorial authentication authorization