Devise
Authentication can be pretty tricky to implement, especially for new Rails developers. Devise is one of the most comprehensive authentication gems, offering key features.
Guide Tasks
  • Read Tutorial

What Devise Provides

Authentication can be pretty tricky to implement, especially for new Rails developers. Devise is one of the most comprehensive authentication gems, offering key features, such as:

  • Registration

  • Signing in / Signing out

  • Session management (The ability to know if a user is signed in as they navigate the application)

  • Forgotten password workflow

  • View helpers/generators (So that you don't have to write all of the view code from scratch)

  • Model generators (Allows for a custom Devise user model to be created with the necessary parameters such as email, password, etc)

  • Single sign on functionality (Giving the ability to integrate Facebook/Twitter/Social sign on capabilities)

  • And quite a bit more

Why use Devise?

I integrate Devise into a large number of the production applications that I build. The reason is because I've found it to be very robust and enables an application to have a fully featured authentication system in a matter of minutes (as opposed to hours/days if built from scratch).

Also, when it comes to security, Devise ships with some of the key elements for ensuring that the authentication forms are secure. From encrypting/decrypting passwords in the database to automatically building in secure ways to reset passwords, Devise does an excellent job for making sure that an application manages authentication securely.

A few drawbacks

One of the main complaints that I have with Devise is what developers are forced to do in order to override default behavior. For example, if you want to have a user sign in with a username instead of their email address it feels a little clunky compared to if you build the auth component from scratch.

Also, if you want to add additional parameters to the Devise module, such as a role, you need to update the ApplicationController file to whitelist the parameters, which always makes me feel a little dirty since it seems like that should be relegated to its own controller file.

Implementation / Devise Example

Let's get started with the integration, to begin, go to rubygems.org and grab the latest stable version of the gem and place it in the Gemfile, at the time of posting I will use:

gem 'devise', '~> 3.5', '>= 3.5.3'

After this run bundle install. Now we can run some of the generators:

rails generate devise:install

That will create the following two files:

create  config/initializers/devise.rb
create  config/locales/devise.en.yml

Before I do anything else, I always open up the initializer file config/initializers/devise.rb and change the default email from address, so change:

config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'

To something like:

config.mailer_sender = 'do-not-reply@railscamp.org'

Now let's look at the instructions that Devise printed to the terminal:

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to 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>

  4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:

       config.assets.initialize_on_precompile = false

     On config/application.rb forcing your application to not access the DB
     or load models when precompiling your assets.

  5. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

Going through the list one by one:

  1. Add a new configuration option for the mailer to the development.rb file (for a production application you would also need to update the config/environments/production.rb file). Inside of the configure block, add in the code below:
# config/environments/development.rb

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  1. Make sure you have a homepage. Since this is a demo application I'll simply create a few basic files, in a real application you'd typically already have a homepage set.
# config/routes.rb

root to: 'pages#home'

Create a controller file app/controllers/pages_controller.rb and add in the basic code:

# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def home
  end
end

And then create a pages directory and add an empty view template file app/views/home.html.erb.

  1. Have flash messages throughout the application. Since we don't care about UI for this demo I'll simply add their base messages to the application.html.erb file above the yield call.
<%#= app/views/layouts/application.html.erb %>

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
  1. We can skip this step since we're wayyyyy past Rails 3.2

  2. Run the Devise view generator, run rails g devise:views in the terminal and it will create all kinds of view template files and forms for the application.

So what did the view template generator give us? Below is the full list of files created:

app/views/devise/shared/_links.html.erb
app/views/devise/confirmations/new.html.erb
app/views/devise/passwords/edit.html.erb
app/views/devise/passwords/new.html.erb
app/views/devise/registrations/edit.html.erb
app/views/devise/registrations/new.html.erb
app/views/devise/sessions/new.html.erb
app/views/devise/unlocks/new.html.erb
app/views/devise/mailer/confirmation_instructions.html.erb
app/views/devise/mailer/password_change.html.erb
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/unlock_instructions.html.erb

Essentially this creates all of the base view template code that we would need for users to be able to: register, sign in, edit their account details, reset their passwords, along with a few optional features, such as confirming accounts.

Now that we have the views all setup, let's create the model, run rails generate devise User in the terminal. This creates a model file along with a migration file. Opening it up we can see that Devise supplies a large number of parameters for the User model, but it lets you customize it prior to finalizing it, let's look at the migration file (your file name will be slightly different depending on the time you run the generator):

# db/migrate/20160115010323_devise_create_users.rb

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    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
end

So you can see that right out of the box Devise gives the standard attributes, such as an email and password, it also provides parameters for tracking user logins, when they last signed in, and a number of other great tools that you can use for user management. By default Devise comments out their confirmable attributes, if you uncomment these users will be sent an email and they need to click on the confirm link prior to their accounts being activated. This is a great feature, however it can be a little buggy since the email could be sent to the user's spam folders and could cause a delay for them to access their account. For that reason I typically don't use the confirmable feature.

Now let's run rake db:migrate and it will update the database schema with our new User model.

If you run the application and navigate to http://localhost:3000/users/sign_up you will be able to register for the application, you may also notice the alerts are working as well, nice work! If you open up another browser window in incognito mode you can navigate to http://localhost:3000/users/sign_in and enter the information you used to sign up and the application will redirect to the homepage and give you the message Signed in successfully.

Cool features

Below are a couple of my favorite built in features for Devise (beyond the simplicity of the implementation).

  1. Automatically knowing if a user is signed in or out

Add the following code to the homepage:

<%#= app/views/pages/home.html.erb %>

<% if current_user %>
  <p>I'm signed in</p>
<% else %>
  <p>I'm not signed in</p>
<% end %>

Close out the incognito browser window and look at the main browser window, hitting refresh you will see the homepage now says I'm signed in, open a new incognito window and go to 'localhost:3000' and you will that it says 'I'm not signed in'. The current_user method is a built in Devise method that you can utilize to see if a user is signed in or out and change the behavior of the application based, pretty cool right?

  1. Forcing a user to sign in

Imagine that you have pages of an application that need to be blocked by non-registered users (or the entire site). Devise supplies a method called authenticate_user! that will ensure that the page(s) can only be seen after a user has logged in. Let's implement this into the application, update the PagesController:

# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def home
    authenticate_user!
  end
end

Now if you refresh the incognito window currently on the homepage (assuming that you're signed out), it won't show the homepage at all and automatically redirects to the login screen and gives the message You need to sign in or sign up before continuing.

Code

Resources