Jack Moore

Email: jack(at)jmoore53.com
Project Updates

Rails and Devise

03 Jul 2019 » rails

Authentication and Authorization are the start of every good web application. Without them, anonimity would rule the internet. But is anonimity really such a bad thing?

Your handwritten authN/Z is not good

The fact is that most people aren’t able to pull off security correctly. I was taught very early on that rolling out my own authn/z is a bad idea due to its complexity. That’s why I use someone else’s library to manage it for me.

The popular authentication libary for Rails is devise. It offers a simple authentication solution for projects, and it is highly configurable and easily customized.

Configuring Devise

Starting with Rails makes the setup pretty easy as all you need to do is add gem devise to your gemfile, run bundle install, and then follow the steps to install the library into your project.

The steps needed to install the library are somewhat simple, but adding some more gems to the Gemfile can significantly improve the devise experience. For me especially, I love the letter-opener gem which allows me to view emails sent in the browser, making for a smoother development experience. I am still working on configuring sendmail for production deploy and integration sendmail with actionmailer, but this is besides the point.

Running a basic rails g devise users adds the basic devise authentication controller methods, migrations and model updates. I also ran a rails g devise admins to generate an admin authentication scheme.

Customizing Devise

Before running my migration on the database to confirm my changes to the users table, I decided to add a couple extra features that devise has to offer to my application. I wanted to be able to track user’s IP’s to keep users informed with the :trackable feature from devise and I also wanted users to be :confirmable via email.

For this, the developers of devise just allowed me to remove the # characters from the lines the model method needed for the integration.

For trackable, this looked like:

    change_table :users do |t|
      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
    end

For confirmable, this looked a little different as I created a migration after the fact. (See more from the Devise Wiki) After generating the migration, I added this code:

  # Note: You can't use change, as User.update_all will fail in the down migration
  def up
    add_column :users, :confirmation_token, :string
    add_column :users, :confirmed_at, :datetime
    add_column :users, :confirmation_sent_at, :datetime
    # add_column :users, :unconfirmed_email, :string # Only if using reconfirmable
    add_index :users, :confirmation_token, unique: true
    # User.reset_column_information # Need for some types of updates, but not for update_all.
    # To avoid a short time window between running the migration and updating all existing
    # users as confirmed, do the following
    User.update_all confirmed_at: DateTime.now
    # All existing user accounts should be able to log in after this.
  end

  def down
    remove_columns :users, :confirmation_token, :confirmed_at, :confirmation_sent_at
    # remove_columns :users, :unconfirmed_email # Only if using reconfirmable
  end

This allowed me to access the methods from within the model after uncommenting the extra methods.

My model looked like the following:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable, :trackable

end

Controller Methods & Authentication

The biggest component devise adds is ensuring the user is authenticated before accessing pieces of information and parts of the API; these are known as controller helpers. Devise doesn’t do authorization very well, but it allows for accessing the current_user and some hooks for authenticating the user before allowing access to controller methods.

The best of these helpers is the authenticate_user! which ensures the user is logged in before being granted access.

A basic controller setup would look like the following:

class UsersController < ApplicationController
    before_action :authenticate_user!
    def index
        @user = current_user
    end
    def show
    end
end

Custom Views, Custom Methods and Routes

Views

Custom views are essential for a better UI experience. I generated general authentication views with rails devise:views. This created a folder views/devise where all the general devise views go. I then created user views with rails g devise:views users which allowed me to customize the user authentication views.

The most important thing to note in this case when generating both views is ensuring you map routes and actions to the correct routes. I have incorrectly mapped routes to the general devise views when I meant to be routing to the user devise views. This became obvious when what I was editing didn’t show when I refreshed for changes.

Custom Methods

Although not used in basic projects, custom devise methods can be used to allow for extra information on sign_up and to fully customize the devise methods. I generated these controller methods with rails g devise:controllers users and it generated all the methods in the user scope within the app/controllers/users/ directory.

Routes

After adding the custom controller methods, I added some changes to my routes.rb file to map to these custom controllers.

Rails.application.routes.draw do
  devise_for :users, controllers: {sessions: 'users/sessions', registrations: 'users/registrations', confirmations: 'users/confirmations'}
end

Conclusion

I would highly recommend devise as an authentication solution for every rails project you create. The devise documentation is comprehensive and there are thousands of blog posts covering the setup and customization of devise.

© Jack Moore