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.