In some of my previous articles about image uploading in Rails, I made mention of Devise but did not go deep into it. In this tutorial, I will be teaching you about Devise.
Ready? Let's get started!
Devise Introduction and Modules
Devise is an authentication solution for Rails built with Warden and provided by the awesome people at Plataformatec. Devise provides different modules:
- Database Authenticatable: This encrypts and stores a password to the database to validate the authenticity of a user while signing in.
- Omniauthable: This attaches OmniAuth support to Devise. Users of your application will be able to sign in using accounts such as Facebook, Twitter, and Google.
- Confirmable: This enables the sending of emails with instructions that will help in the verification of an account.
- Recoverable: This module helps in times when users forget their password and need to recover it. With this, the user will be able to reset the password.
- Registerable: This handles the signup of users. It also allows users to edit and delete their accounts.
- Rememberable: This module makes it possible for your application to remember a logged-in user by storing a cookie.
- Trackable: This module helps track sign-in count, timestamps, and IP address.
- Timeoutable: This module is responsible for expiring a session that has not been active for a period of time.
- Validatable: With this module, email and password get to be validated.
- Lockable: This provides an extra layer of security—when activated, an account can be locked after a given number of failed sign-in attempts.
Devise Integration
For the purpose of this tutorial, we are going to generate a Rails application that we'll use to check out the workings of Devise. Let's proceed!
rails new devise-app -T
The -T
flag tells Rails to generate the application without the default test suite. Navigate to your application directory and drop the following gems into your Gemfile
.
#Gemfile gem 'devise', '~> 4.1' gem 'bootstrap-sass', '~> 3.3'
Now install the Devise and Bootstrap gems you just added.
bundle install
Rename your app/assets/stylesheets/application.css
file to app/assets/stylesheets/application.scss
and add the following lines in it:
#app/assets/stylesheets/application.scss @import "bootstrap-sprockets"; @import "bootstrap";
Open up the app/assets/javascripts/application.js
file and require bootstrap-sprockets
. Mine looks like this:
#app/assets/javascripts/application.js //= require jquery //= require bootstrap-sprockets //= require jquery_ujs //= require turbolinks //= require_tree .
Next, you need to run the Rails command to install the configuration files for Devise. You do so by running this command:
rails generate devise:install
The command generates the following on your terminal. You should read it to understand what happened.
create config/initializers/devise.rb create config/locales/devise.en.yml =============================================================================== 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 ===============================================================================
The command also generates two files, which you can find in the config
directory. It also gives us some instructions on what we should do.
Navigate to your application layout, app/views/layouts/application.html.erb
, and make it look like what I have below:
#app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>DeviseApp</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> </head> <body> <div class="container-fluid"> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> </div> <div class="container-fluid"> <%= yield %> </div> </body> </html>
You need to define the default URL options for your development environment. Add the code below in config/environments/development.rb
.
#config/environments/development.rb config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Now you need to create a User
model for Devise. You can do so using your terminal.
rails generate devise User
This will generate a user.rb
file in your app/models
directory. The file generated will look like this:
class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable end
You can see that it contains the default modules I mentioned above. The command you ran also modified your config/routes.rb
file by adding a route for devise. You should check that out.
At this point, you need to migrate your database. You do so by running:
rake db:migrate
Authentication Using Devise
Now you need to create a PagesController
and wrap Devise authentication around it—this will prevent unauthorized persons from seeing the page.
rails generate controller Pages index
Open up your routes file and set the root of your application.
#config/routes.rb Rails.application.routes.draw do devise_for :users root to: "pages#index" end
Open up your PagesController
and add authentication for your index
and new
pages.
#app/controllers/pages_controller.rb class PagesController < ApplicationController before_action :authenticate_user!, only: [:index, :new] def index end def new end end
The code shows that the index
and new
pages are accessible only to registered users. Open up your terminal and start your rails server
. Point your browser to http://localhost:3000
and you will automatically be redirected to the Devise sign-in page.
Signing in Without Using Email
The default means of signing into Devise involves the use of email address and password. What if you want to enable users to sign in with their unique username? If that is what you want, it is possible. Let's see how.
Run the command:
rails generate migration AddUsernameToUSers username:string
This will add a new column for username
in your users
table. Migrate your database.
rake db:migrate
You need to add a field to your views where your users can enter their username. When you go to your app/views
directory, you will not find any file that renders the Devise views. This is because Devise loads the views from its gemset. To customize it, you have to generate copies of the views. The command below does the magic.
rails generate devise:views
This will generate some folders and files in your app/views
directory.
You will need to edit the page for signing in, signing up, and updating user information. Just paste the blocks of code below into their respective files.
Sign-Up
#app/views/devise/registrations/new.html.erb <h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= devise_error_messages! %> <div class="form-group"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, class: "form-control" %> </div> <div class="form-group"> <%= f.label :username %> <%= f.text_field :username, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "off", class: "form-control" %> </div> <div class="form-group"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %> </div> <div class="actions"> <%= f.submit "Sign up", class: "btn btn-primary" %> </div> <% end %> <%= render "devise/shared/links" %>
Edit
#app/views/devise/registrations/edit.html.erb <h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div class="form-group"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, class: "form-control" %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="form-group"> <%= f.label :username %> <%= f.text_field :username, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.password_field :password, autocomplete: "off", class: "form-control" %> </div> <div class="form-group"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %> </div> <div class="form-group"> <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password, autocomplete: "off", class: "form-control" %> </div> <div class="actions"> <%= f.submit "Update", class: "btn btn-primary" %> </div> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p> <%= link_to "Back", :back %>
Sign-In
#app/views/devise/sessions/new.html.erb <h2>Log in</h2> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <div class="form-group"> <%= f.label :username %><br /> <%= f.text_field :username, autofocus: true, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %><br /> <%= f.password_field :password, autocomplete: "off", class: "form-control" %> </div> <% if devise_mapping.rememberable? -%> <div class="form-group"> <%= f.check_box :remember_me %> <%= f.label :remember_me %> </div> <% end -%> <div class="actions"> <%= f.submit "Log in", class: "btn btn-primary" %> </div> <% end %> <%= render "devise/shared/links" %>
Using your text editor, navigate to app/controllers/application_controller.rb
. You need to modify it to permit the use of username. Modify it to look like this:
#app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters added_attrs = [:username, :email, :password, :password_confirmation, :remember_me] devise_parameter_sanitizer.permit :sign_up, keys: added_attrs devise_parameter_sanitizer.permit :account_update, keys: added_attrs end end
Now a user can sign in with his/her username. At this point there is something not right about your application. When a user signs in, there is no way of signing out. This does not result in a great user experience. I'll show you how to fix that.
From your terminal, create a new directory called shared
in your app/views
folder.
mkdir app/views/shared touch app/views/shared/_navigation.html.erb
The file you created above is a partial where the code for your navigation bar will be written. Drop in the following code.
#app/views/shared/_navigation.html.erb <nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Tutsplus Devise', root_path, class: 'navbar-brand' %> </div> <div id="navbar"> <ul class="nav navbar-nav"> <li><%= link_to 'Home', root_path %></li> </ul> <ul class="nav navbar-nav pull-right"> <% if user_signed_in? %> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#"> <%= current_user.name %> <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> <li><%= link_to 'Profile', edit_user_registration_path %></li> <li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li> </ul> </li> <% else %> <li><%= link_to 'Log In', new_user_session_path %></li> <li><%= link_to 'Sign Up', new_user_registration_path %></li> <% end %> </ul> </div> </div> </nav>
Now you need to render the navigation bar in your application layout. Open up app/views/layouts/application.html.erb
and drop in the code to render your navigation bar.
#app/views/layouts/application.html.erb ... <div class="container-fluid"> <%= render "shared/navigation" %> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> </div> ...
Conclusion
In this part you learned how to install Devise and add authentication to your pages. I also made mention of a partial. I will cover that in a separate tutorial.
In the next part, we will cover some areas more advanced than this. I hope this was worth your time!
Comments