User Authentication in Rails
Our Goal
In this article I'd like to discuss how to implement user authentication into this blog. In this article about modelling the data for our blog we talked about wanting to keep track of Authors, and that they would be stored in the User model. This is the user that will need to authenticate into the system.
By having the user authenticate, we can give them access to the admin section of the website, which has security and permissions attached to it.
User Authentication in Rails
There are a number of ways you can include authentication on a website. You can opt for basic http auth which is quite easy to implement in Ruby on Rails, but for this I wanted to have the authentication tied to a User account in the system, with a proper login form.
The two most common gems for handling user authentication are Devise and Sorcery. Both are more than capable, supported by the community, and offer an easy way to login using external services (Facebook, Twitter), but they are fairly different when it comes to ideology.
Devise acts as a Rails Engine, which already has controllers, actions, and views, which you can override if you want to, or you can use them with the default functionality that they come with.
Sorcery is much more light-weight, and provides you with a number of helper methods that you can use, but you're on your own in terms of setting up controllers, actions, and views for handling logging in and logging out of the website.
I decided to choose Sorcery for this project, mostly because I have worked with Devise before on others and wanted to try it out. In the end I much preferred having the freedom that Sorcery provided, able to create my own controllers rather than having to figure out what the correct way was to override the one that came in Devise.
Setting Up Rails for Sorcery
You will first want to add Sorcery to your Gemfile.
gem 'sorcery'
After installing Sorcery you can run this command, which will create a User model for you along with some DB migrations.
rails generate sorcery:install remember_me reset_password
Because I already had a users table, I had to modify the migrations that Sorcery created, which ended up looking something like this:
class SorceryRememberMe < ActiveRecord::Migrationdef changeadd_column :users, :crypted_password, :string, :null => falseadd_column :users, :salt, :string, :null => falseadd_column :users, :remember_me_token, :string, :default => niladd_column :users, :remember_me_token_expires_at, :datetime, :default => niladd_column :users, :reset_password_token, :string, :default => niladd_column :users, :reset_password_token_expires_at, :datetime, :default => niladd_column :users, :reset_password_email_sent_at, :datetime, :default => niladd_index :users, :remember_me_tokenadd_index :users, :reset_password_tokenendend
Ensure that you have this line of code in your User model: authenticates_with_sorcery!
Logging In and Out
I then created some routes for logging in and out of the system.
get 'login', to: 'user_sessions#new', as: :loginpost 'login', to: 'user_sessions#create'get 'logout', to: 'user_sessions#destroy', as: :logout
These routes point to the controller that is below. This controller uses a couple helper methods that Sorcery provides, such as login
and logout
. Other than that it is just normal Rails code.
class UserSessionsController < ApplicationControllerdef new@user = User.newenddef createif @user = login(user_params[:email], user_params[:password])redirect_back_or_to(admin_root_path, notice: 'Login successful')else@user = User.newflash.now[:alert] = 'Login failed'render action: 'new'endenddef destroylogoutredirect_to root_path, notice: "You've been logged out"endprivatedef user_paramsparams.require(:user).permit(:email, :password)endend
Requiring Auth and Checking If Authenticated
Lastly, Sorcery provides a before action filter which you can put in your controllers that require authentication to access. This can take the form of the following code below:
class Admin::BaseController < ApplicationControllerbefore_filter :require_loginprivatedef not_authenticatedredirect_to login_path, alert: "Please login first"endend
The not_authenticated method will be called if the user is not yet authenticated. To check whether there is currently an authenticated user, you can call logged_in?
(available in the view) which will return a Boolean, or current_user
(available in view) which will return the current User or nil if they aren't authenticated.
Not Yet Implemented
I haven't yet implemented the forgotten password functionality nor the remember me functionality, purely because this is for my own blog and I haven't required it yet :) I already have the required DB migrations in place, so it won't be much work to do when I get around to it (I hope!).