Devise Github project page: https://github.com/plataformatec/devise
After some google searching I arrived at the decision to use Devise over Authlogic for my brand spanking new rails 3 app. I haven’t really had that much experience with authlogic in the past, besides using it in my previous rails 3 app. It was a little tricky to get it working with rails 3 and I wasn’t the biggest fan of the documentation (I felt it could have been a little more granular). That being said I have never used Devise but the yammering on the internet suggests it is a cleaner solution and works out of the box with rails 3. (http://stackoverflow.com/questions/4136121/rails-3-authentication-authlogic-vs-devise) I also liked some of the out of the box features it came with:
- Database Authenticatable: encrypts and stores a password in the database to validate the authenticity of an user while signing in. The authentication can be done both through POST requests or HTTP Basic Authentication.
- Token Authenticatable: signs in a user based on an authentication token (also known as “single access token”). The token can be given both through query string or HTTP Basic Authentication.
- Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
- Recoverable: resets the user password and sends reset instructions.
- Registerable: handles signing up users through a registration process, also allowing them to edit and destroy their account.
- Rememberable: manages generating and clearing a token for remembering the user from a saved cookie.
- Trackable: tracks sign in count, timestamps and IP address.
- Timeoutable: expires sessions that have no activity in a specified period of time.
- Validatable: provides validations of email and password. It’s optional and can be customized, so you’re able to define your own validations.
- Lockable: locks an account after a specified number of failed sign-in attempts. Can unlock via email or after a specified time period.
- Encryptable: adds support of other authentication mechanisms besides the built-in Bcrypt (the default).
The Exploration
First things first, add “devise” to your rails Gemfile in the root directory of your application.
Note: this example assumes knowledge of the bundler gem
bundle install
Running bundle install will set you up with:
Installing bcrypt-ruby (2.1.3) with native extensions
Installing warden (1.0.3)
Installing devise (1.1.5)
Following the install docs on the Devise project page..
rails generate devise:install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Some setup you must do manually if you haven't yet:
1. Setup default url options for your specific environment. Here is an
example of development environment:
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
This is a required Rails configuration. In production it must be the
actual host of your application
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root :to => "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
===============================================================================
Alright, based on the install output it looks like we need to do a little setup work… However, I will leave that to the reader since they may be using a pre-existing rails application…
After you are finished configuring the application its time to create your user model.
rails generate devise User
invoke active_record
create app/models/user.rb
invoke test_unit
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate/20110108192129_devise_create_users.rb
inject app/models/user.rb
route devise_for :users
Great, lets take a look at what the generator created starting with the migration.
class DeviseCreateUsers < ActiveRecord::Migration def self.up create_table(:users) do |t| t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
# 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
It looks like Devise gives you password encryption/storing, password recovery, session tokens, and login attempts by default. For my application I will also uncomment the lines for t.confirmable, t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both and t.token_authenticatable since I find these registration options useful out of the box and will save me time from writing those components later (not to say I still wont…).
The only thing I needed to investigate of these options was the lockable option since I wanted to understand the lock and unlock strategies.
:unlock_strategy - The strategy used for unlock. Can be :time, :email, :both (default), :none.
If :email or :both, creates a unlock_token field.
:lock_strategy - The strategy used for locking. Can be :failed_attempts (default) or :none.
Alright, now that we understand what they mean here is what my final migration looks like:
class DeviseCreateUsers < ActiveRecord::Migration def self.up create_table(:users) do |t| t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
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
Now let’s take a look at our User model.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
end
It looks like based on our migration options that we will also have to uncomment token_authenticatable, :confirmable, :lockable and :timeoutable and add it to the devise option.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable, :confirmable, :lockable, :timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
end
We don’t need to setup our routes for user because devise has already added devise_for :users to our config/routes.rb for us. If we then run rake routes from the command line we get:
new_user_session GET /users/sign_in(.:format) {:action=>"new", :controller=>"devise/sessions"}
user_session POST /users/sign_in(.:format) {:action=>"create", :controller=>"devise/sessions"}
destroy_user_session GET /users/sign_out(.:format) {:action=>"destroy", :controller=>"devise/sessions"}
user_password POST /users/password(.:format) {:action=>"create", :controller=>"devise/passwords"}
new_user_password GET /users/password/new(.:format) {:action=>"new", :controller=>"devise/passwords"}
edit_user_password GET /users/password/edit(.:format) {:action=>"edit", :controller=>"devise/passwords"}
user_password PUT /users/password(.:format) {:action=>"update", :controller=>"devise/passwords"}
user_registration POST /users(.:format) {:action=>"create", :controller=>"devise/registrations"}
new_user_registration GET /users/sign_up(.:format) {:action=>"new", :controller=>"devise/registrations"}
edit_user_registration GET /users/edit(.:format) {:action=>"edit", :controller=>"devise/registrations"}
user_registration PUT /users(.:format) {:action=>"update", :controller=>"devise/registrations"}
user_registration DELETE /users(.:format) {:action=>"destroy", :controller=>"devise/registrations"}
user_confirmation POST /users/confirmation(.:format) {:action=>"create", :controller=>"devise/confirmations"}
new_user_confirmation GET /users/confirmation/new(.:format) {:action=>"new", :controller=>"devise/confirmations"}
user_confirmation GET /users/confirmation(.:format) {:action=>"show", :controller=>"devise/confirmations"}
user_unlock POST /users/unlock(.:format) {:action=>"create", :controller=>"devise/unlocks"}
new_user_unlock GET /users/unlock/new(.:format) {:action=>"new", :controller=>"devise/unlocks"}
user_unlock GET /users/unlock(.:format) {:action=>"show", :controller=>"devise/unlocks"}
root /(.:format) {:controller=>"home", :action=>"index"}
Finally it’s time to migrate our database and create our user model.
rake db:migrate
== DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
NOTICE: CREATE TABLE will create implicit sequence "users_id_seq" for serial column "users.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "users_pkey" for table "users"
-> 0.0553s
-- add_index(:users, :email, {:unique=>true})
-> 0.0020s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0021s
-- add_index(:users, :confirmation_token, {:unique=>true})
-> 0.0115s
-- add_index(:users, :unlock_token, {:unique=>true})
-> 0.0028s
== DeviseCreateUsers: migrated (0.0740s) =====================================
Now it’s time to start our rails server and see what devise has given us. Run rails server from the command line and lets travel to “localhost:3000/users/sign_up” and see whats going on.
My first thoughts are “Wow, that’s kind of ugly”. haha. Looks like down the line I will have to override those views and build custom ones (by overriding the routes!). I then attempt to sign up by filling out the form. The page reloads and tells me “You have signed up successfully. If enabled, a confirmation was sent to your e-mail.”. Yay, alright now I realize immediatly that I haven’t yet setup an smtp server on my local machine however if we check the server output or logs we will see a generated message:
Sent mail to [email protected] (58ms)
Date: Sat, 08 Jan 2011 12:04:04 -0800
From: [email protected]
To: [email protected]
Message-ID: <[email protected]>
Subject: Confirmation instructions
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p>Welcome [email protected]!</p>
<p>You can confirm your account through the link below:</p>
<p><a href="http://localhost:3000/users/confirmation?confirmation_token=wYQlv1D1lbB9InFq8dVZ">Confirm my account</a></p>
Sent mail to [email protected] (58ms)Date: Sat, 08 Jan 2011 12:04:04 -0800From: [email protected]: [email protected]: <[email protected]>Subject: Confirmation instructionsMime-Version: 1.0Content-Type: text/html; charset=UTF-8Content-Transfer-Encoding: 7bit
<p>Welcome [email protected]!</p>
<p>You can confirm your account through the link below:</p>
<p><a href="http://localhost:3000/users/confirmation?confirmation_token=wYQlv1D1lbB9InFq8dVZ">Confirm my account</a></p>
Fantastic if we go to the confirmation address http://localhost:3000/users/confirmation?confirmation_token=wYQlv1D1lbB9InFq8dVZ we should be able to active our new account and sure enough the confirm works and sends us to the homepage. It would have been nice to tell the users that they had been confirmed but I guess this is left as an exercise for the developer.
Conclusion
Devise is easy to setup and install using Rails 3. It give me the flexibility and features I need to fulfill the authentication requirements of my application. Good documentation and setup instructions go a long way. We will see with time if I regret my decision. If anyone out there in the internets know of any helpful information that would be beneficial to this blog post please comment below!
Update
To update your Devise views run this from the command line:
rails generate devise:views
This will pull the views from the gem and allow you to modify them.