Integrating Authentication
We're going to start the feature build out of our application by integrating authentication into the application by leveraging the Devise gem.
Guide Tasks
  • Read Tutorial

Why Start with Authentication?

In picking out what features to start building first I try to prioritize features based on dependencies. By dependencies I mean what feature has the most other features depend on it?. Thankfully we did a good job in creating our requirements document, which means that we can look at each of the functional requirements to see what feature the other modules in the application will rely on.

large

Do you notice a trend? Over half of the entire application requirements seem to be based on User requirements, so that is where we'll start. If we waited to integrate other features, such as the Post functionality we'd have to go back later on and refactor the posting feature to reference the User model.

In addition to analyzing the functional requirements, I also look at the proposed database schema. Do you notice how many times the other tables will have a user_id foreign key? That's also a good sign that we should build our User model should be the first feature to build.

Devise or Auth from Scratch?

I've been building applications for quite a while and I still have only had two applications that had to have authentication created from scratch as opposed to using a gem like Devise and both of those times were due to a requirement to integrate Microsoft's ActiveDirectory auth engine.

My personal opinion on this is that I prefer to use well tested and constantly maintained gems like Devise over building a feature from scratch, assuming that the gem is flexible and doesn't inhibit any future features for the application. I'm going to go through each of the steps needed for integrating Devise, but I'm not going to go into detail on the gem itself, I already wrote a Devise gem review that you want some more detailed information about the gem itself.

Implementing Authentication

We could start by creating some model specs, however I don't like writing specs to test functionality that I know is going to pass, Devise has a comprehensive test suite and it would be a waste of our time to duplicate the tests that they already have in place. You can see the gem test suite here.

Before we start building the feature, it's important that we're not working on the master branch of our repo, so first create a new branch by running the terminal command:

git checkout -b add-auth

This is important because we need to have the ability to isolate the work we're doing so it doesn't touch the master branch of our repo, even locally. I can't tell you how many times I've been working on a feature and right when I'm in the middle of it I'm asked to perform an urgent task, such as fixing a bug on the live application. If I didn't check my new work into a separate branch I'd have to:

  • Reset to a different commit (and potentially cause conflicts)

  • Comment out my changes (this is a horrible idea, don't do it, you'll inevitably forget to comment one line out that breaks the entire application)

  • Manually remove all of the changes (also very error prone and I'd lose all my hard work!)

Now that we're safely working on an isolated branch, let's install the gem by adding it to the gemfile:

# Gemfile

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

After running bundle we can run the installer:

rails generate devise:install

First let's update the default mailer_sender configuration setting in config/initializers/devise.rb to ensure that auth emails sent from the application reference our app name and not the default address:

# config/initializers/devise.rb

config.mailer_sender = 'team@dailysmarty.com'

Let's go through the standard configuration steps:

Update the local mail server

# config/environments/development.rb

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

Update the local mail server

# config/environments/development.rb

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

We can skip the instructions for the homepage since we did that in the Rails App Configuration lesson. Now let's add in the alerts. I don't add the alerts to the master layout file since there could be pages where we don't want the alerts showing up, or we may want them rendered on a special spot on the page. So let's create a view directory called shared/ and add in a partial called _alerts.html.erb:

<%#= app/views/shared/_alerts.html.erb %>

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

Now we can simply add a render call in the application layout file, and we'll remove it when we start to implement the design:

<%#= app/views/layouts/application.html.erb %>

<%= render 'shared/alerts' %>

Notice how much more cleaner this is? As we build this application you'll see that we'll be using partials quite a bit to ensure our views stay as clean and DRY as possible. Now let's create our views by running the following terminal command:

rails g devise:views

This will create all of the view files that we'll need to manage:

  • Registrations

  • Sign in

  • Editing accounts

  • Requesting a password reset

  • Mailer templates

I like the Devise generator, however it does create a few files that we don't need, let's remove those so our app doesn't start getting cluttered with code that we're not going to use:

rm -rf app/views/devise/confirmations
rm -rf app/views/devise/mailer/confirmation_instructions.html.erb

We're not going to be using the confirmation email workflow provided by Devise so there's no need to keep these files.

Now let's create our User model:

rails g devise User first_name:string last_name:string avatar:text username:string

This will create the default User model provided by the gem and I also appended the first_name, last_name, avatar, and username attributes so they will get added to the users table. Now we can run rake db:migrate to create the table and update the schema. Opening the schema file up we can see our table and attributes are all listed:

# db/schema.rb

create_table "users", force: :cascade do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet     "current_sign_in_ip"
    t.inet     "last_sign_in_ip"
    t.string   "first_name"
    t.string   "last_name"
    t.text     "avatar"
    t.string   "username"
    t.datetime "created_at",                          null: false
    t.datetime "updated_at",                          null: false
end

add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree

This will get us everything we need for users to start registering, signing in, and other standard authentication feature tools. In the next lesson we're going to walk through building custom functionality into our auth system.

Resources