- 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:
- Add a new configuration option for the mailer to the
development.rb
file (for a production application you would also need to update theconfig/environments/production.rb
file). Inside of theconfigure
block, add in the code below:
# config/environments/development.rb config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
- 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
.
- 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 theyield
call.
<%#= app/views/layouts/application.html.erb %> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p>
We can skip this step since we're wayyyyy past Rails 3.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).
- 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?
- 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.