Rails 3 Authentication and Authorization with Devise and CanCan (Part 1)

Nov 26 2010 Published by thepanakawan under Tech

Recently, I was involved in a new project that uses Rails 3. That was my first Rails 3 project, I’ve always used Rails 2 before. Therefore, I tried to get accustomed to how Rails 3 works and how it differs from Rails 2. One thing that I really had to change back then was what authentication and authorization plugin to use. In my previous projects, I always used Authlogic and Lockdown. When I started this particular project, it seemed that both Authlogic and Lockdown was not Rails 3 ready (but I heard now both are Rails 3 ready). So I decided to give a try to different plugins. That’s how I found Devise and CanCan.

Most part of this writing is heavily duplicated from tutorials from Devise’s official github page, Asciicast’s episode on CanCan by Ryan Bates, and Tony Amoyal’s tutorial. I took most of the codes from these resources and then slightly modified it to meet my own needs. In this post, I try to share my experience working with both Devise and CanCan. I’ll explain it in a step-by-step style, maybe some steps are just too obvious for expert readers, please bear with it.

The Project

I wanted to have a user management feature. In the project, we had several roles with predefined sets of features that each role can access. The system has an administrator roles. Users can’t freely register to the system, the admins add them to the system and then assign a role to them. In the actual system I worked with, users passwords are generated by the application and then sent to the respective user’s mail. To keep this post simple, I’ll make administrator set the password directly.

1. Adding needed gems

Add these lines to your Gemfile:

1
2
gem 'devise'
gem 'cancan'

then install the gems with this command from your command line:

1
bundle install

2. Adding Devise to your project

Run these commands from your command line:

1
2
rails generate devise:install
rails generate devise User

3. Modifying user model and migration file

As said before, I needed the user management feature to be able to add, delete, and modify users through a CRUD mechanism. Therefore, there are several things we need to change in our user model and migration file. First, we delete the “registerable” attribute.  Second, we add a “username” attribute to the model and to the migration file. Here is how your user model should look like:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable, :lockable and :timeoutable
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :username, :password, :password_confirmation, :remember_me
end

and your migration file for user should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      t.recoverable
      t.rememberable
      t.trackable
      t.string :username

      # t.confirmable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
      # t.token_authenticatable


      t.timestamps
    end

    add_index :users, :email,                :unique => true
    add_index :users, :reset_password_token, :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    # add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

4. Modifying the routes

When we installed Devise, it added it’s own routes to our routes.rb file. Since we want to have CRUD ability, we have to add another routes for our user resources. So this is what we do:

1
2
3
4
5
6
DeviseTest::Application.routes.draw do
  devise_for :users
  resources :users

  # add another lines as you need...
end

5. Creating the controller and views

In this step, we can just use a generic controller and views template that usually created when you use rails generator to do a scaffolding. Anyway, this is how my users_controller.rb looks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class UsersController < ApplicationController
  # GET /users
  # GET /users.xml
  def index
    @users = User.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @users }
    end
  end

  # GET /users/1
  # GET /users/1.xml
  def show
    @user = User.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @user }
    end
  end

  # GET /users/new
  # GET /users/new.xml
  def new
    @user = User.new
    @current_method = "new"

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @user }
    end
  end

  # GET /users/1/edit
  def edit
    @user = User.find(params[:id])
  end

  # POST /users
  # POST /users.xml
  def create
    @user = User.new(params[:user])

    respond_to do |format|
      if @user.save
        format.html { redirect_to(@user, :notice => 'User was successfully created.') }
        format.xml  { render :xml => @user, :status => :created, :location => @user }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /users/1
  # PUT /users/1.xml
  def update
    @user = User.find(params[:id])

    respond_to do |format|
      if @user.update_attributes(params[:user])
        format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /users/1
  # DELETE /users/1.xml
  def destroy
    @user = User.find(params[:id])
    @user.destroy

    respond_to do |format|
      format.html { redirect_to(users_url) }
      format.xml  { head :ok }
    end
  end
end

Again, although this is a generated view, I’ll include it here anyway. Here’s our _form.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% @user.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>
  <% if @current_method == "new" %>
    <div class="field">
      <%= f.label :password %><br />
      <%= f.password_field :password %>
    </div>
    <div class="field">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>
  <% end %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Our edit.html.erb:

1
2
3
4
5
6
<h1>Editing user</h1>

<%= render 'form' %>

<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>

Our index.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<h1>Listing users</h1>

<table>
  <tr>
    <th>Email</th>
    <th>Username</th>
  </tr>

  <% @users.each do |user| %>
    <tr>
      <td><%= user.email %></td>
      <td><%= user.username %></td>
      <td><%= link_to 'Show', user %></td>
      <td><%= link_to 'Edit', edit_user_path(user) %></td>
      <td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<br />

<%= link_to 'New user', new_user_path %>

Our new.html.erb:

1
2
3
4
5
<h1>New user</h1>

<%= render 'form' %>

<%= link_to 'Back', users_path %>

And the last… our show.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<p id="notice"><%= notice %></p>

<p>
  <b>Email:</b>
  <%= @user.email %>
</p>

<p>
  <b>Username:</b>
  <%= @user.username %>
</p>


<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

6. Using the Devise

As noted by Peter in the comment section, I forget to put this step in my original post. In order Devise to really work, we have to put this line in every controller that can only be accessed by authenticated users.

1
before_filter :authenticate_user!

Next Step

Well, it’s a long post already… . I guess it’s better for us to take a break. So, this is the first part. We have not used CanCan yet, but now we have a working user management feature as we wanted it in the beginning. Hope this post helps you with your own project using Devise. I’ll write the second part where we will use CanCan as soon as possible.

9 responses so far

Leave a Reply