Adding Authentication
In the previous guide you built a simple blogging engine using Rails and published it on Heroku. If you haven’t run through that post then you should do so now before starting this one. If you feel confident with Rails but want to learn more about authentication you can find some instructions on getting the code set up properly below.
Introduction to Authentication
Authentication is the process of identifying a user. This is usually based on a username and password, which is what we will be using here.
You have been using gems, and making changes to your Gemfile, in the previous guide without any real explanation. Put simply, a gem provides a specific piece of functionality for your application. It is packaged as a library that contains the code for the functionality you require, together with any files or assets related to that functionality.
In this guide, we will be using a gem called Devise to add authentication to our blog application. The devise gem features in pretty much every Rails application you will ever come across so listen up and pay attention! :-)
Setup
Add the gem devise
to your Gemfile
so it looks like this:
(Windows users, if you added it previously, don’t forget to keep the coffee-script-source gem in the list)
source 'https://rubygems.org' git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "https://github.com/#{repo_name}.git" end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.1.6' # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use Puma as the app server gem 'puma', '~> 3.7' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use devise for authentication gem 'devise' # Use CoffeeScript for .coffee assets and views gem 'coffee-rails', '~> 4.2' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.5' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Save the file and run bundle install
This will add devise to our Rails application. (If you are on Windows, and have an error message about bcrypt_ext, see below).
Devise is the most popular authentication solution testing tool which provides a special testing language (powered by Ruby) for testing existing code and for informing developers about the structure and functionality of yet to be written code!
To complete the installation run:
rails generate devise:install
Windows bcrypt_ext error
Follow the steps below to fix this issue:
- Run
gem uninstall bcrypt
(select yes to go through with it) - Run
gem install bcrypt --platform=ruby
- Open
Gemfile
and add the linegem ‘bcrypt’, platforms: :ruby
- Run
bundle install
- Restart your server
Configuring Devise
One of the many useful features of devise is that it sends confirmation emails to your users. For this to work, we need to set up the default URL options in each environment. Let’s start with development and leave test and production for later.
Open config/environments/development.rb
and add the following line right at the end (before the keyword end
):
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
We also need to display any alerts that devise might generate. To do this, open app/views/layouts/application.html.erb
and make it look like the following:
# app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>QuickBlog</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <% if notice %> <p class="alert alert-success"><%= notice %></p> <% end %> <% if alert %> <p class="alert alert-danger"><%= alert %></p> <% end %> <%= yield %> </body> </html>
Add the User Model
Authentication doesn’t make much sense unless we have the concept of a User.
Run the following commands one by one:
rails generate devise user
rails db:migrate
This will create a User table in our database and a User model in our application to talk to it.
Create your first user
A user table isn’t much use without a user, so let’s create one now.
First, make sure your server is running. If your server is already running, restart it so that the new configuration is loaded.
Then, navigate to http://localhost:3000/users/sign_up and create a user account.
Add sign-up and login links
You have probably noticed many websites with a link in the top right corner advising you that you are logged in, or providing you with a link to sign up. Let’s add that to our app!
We want those links to appear on every page in our entire application. Fear not, we do not have to go and add it one by one to every page. Rails provides a default layout for your entire application, called application.html.erb
. This is where you put HTML that you want to include on every page of your website.
Open app/views/layouts/application.html.erb
and make it look like the following:
<!DOCTYPE html> <html> <head> <title>QuickBlog</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <p class="navbar-text pull-right"> <% if user_signed_in? %> Logged in as <strong><%= current_user.email %></strong>. <%= link_to 'Edit profile', edit_user_registration_path, :class => 'navbar-link' %> | <%= link_to "Logout", destroy_user_session_path, method: :delete, :class => 'navbar-link' %> <% else %> <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %> | <%= link_to "Login", new_user_session_path, :class => 'navbar-link' %> <% end %> </p> <% if notice %> <p class="alert alert-success"><%= notice %></p> <% end %> <% if alert %> <p class="alert alert-danger"><%= alert %></p> <% end %> <%= yield %> </body> </html>
You will note that this code checks for any alerts and displays them if found. We have similar code in show.html.erb
and index.html.erb
, so it is best to remove it to avoid the same message being repeated twice on your page.
Open show.html.erb
and remove the line <p id="notice"><%= notice %></p>
.
If a user is not logged in, we need to redirect them to the login page. Open app/controllers/application_controller.rb
and add:
before_action :authenticate_user!
just before the end
keyword so that it looks like:
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :authenticate_user! end
application_controller
is a project-wide file, so those settings will apply to the entire app. However, we can override it on a case-by-case basis.
In the case of blog posts; anybody can read our blogs, we simply don’t want to allow them to create or edit (or delete). To do this, open app/controllers/posts_controller.rb
and add:
before_action :authenticate_user!, except: [:show, :index]
just after the first before_action
so that it looks like:
# app/controllers/posts_controller.rb class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update, :destroy] before_action :authenticate_user!, except: [:show, :index] # GET /posts # GET /posts.json def index @posts = Post.all end # GET /posts/1 # GET /posts/1.json def show end # GET /posts/new def new @post = Post.new end # GET /posts/1/edit def edit end # POST /posts # POST /posts.json def create @post = Post.new(post_params) respond_to do |format| if @post.save format.html { redirect_to @post, notice: 'Post was successfully created.' } format.json { render :show, status: :created, location: @post } else format.html { render :new } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # PATCH/PUT /posts/1 # PATCH/PUT /posts/1.json def update respond_to do |format| if @post.update(post_params) format.html { redirect_to @post, notice: 'Post was successfully updated.' } format.json { render :show, status: :ok, location: @post } else format.html { render :edit } format.json { render json: @post.errors, status: :unprocessable_entity } end end end # DELETE /posts/1 # DELETE /posts/1.json def destroy @post.destroy respond_to do |format| format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_post @post = Post.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def post_params params.require(:post).permit(:title, :body) end end
To view our changes, make sure your server is running and navigate to http://localhost:3000. You should see your login status at the top of the page.
Cleaning up
We’ve now secured our application by adding authentication. We’ve increased our confidence in our existing code and we’re ready to move on to adding some automated testing to our blog. Before we do that though we should commit our code.
Run:
git add .
git commit -m "Adding devise gem for authentication"
Next Steps
Up next we’re going to add some automated testing to the blogging engine. Head on over to Part 3: Testing the Blog to get started.
If you’re interested in more training from reinteractive, or just want to give us some feedback on this you can leave a comment below or:
Sign up to our Training mailing list.
Just put your email below and we’ll let you know if we have anything more for you. We hate spam probably more than you do so you’ll only be contacted by us and can unsubscribe at any time:
Do Development Hub
Sign up for DevelopmentHub. We’ll guide you through any issues you’re having getting off the ground with your Rails app.
Or just
Tweet us @reinteractive. We’d love to hear feedback on this series, do you love it? Want us to do more? Let us know!