Today, I’d like introduce you to AuthLogic, a simple authentication solution for Ruby built by Ben Johnson.
AuthLogic is unobtrusive and pretty low-level. It does not rely on generated code (like Devise, for example) – instead it provides only the tools that you can use to code your app the way you like. In a sense, AuthLogic is similar to Sorcery.
Let’s build a Rails app to allow users to register, sign in,and out, as well as reset their password. While doing this, we will discuss AuthLogic’s various features and settings. After reading this tutorial you will be ready to use AuthLogic in your real projects.
Source code can be found on GitHub.
Demo app is available at sitepoint-authlogic.herokuapp.com.
Getting Started
The demo app that I am going to show you is built with Rails 4.1, but AuthLogic supports Rails 3, and even Rails 2 (there is a separate branch for it).
Create a new “Logical” app without the default testing suite:
$ rails new Logical -T
Drop in the following gems:
Gemfile
[...]
gem 'bootstrap-sass'
gem 'authlogic', '3.4.6'
[...]
and run
$ bundle install
Take advantage of Bootstrap’s styles if you wish:
stylesheets/application.scss
@import 'bootstrap-sprockets';
@import 'bootstrap';
and modify the layout:
views/layouts/application.html.erb
[...]
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<%= link_to 'Logical', root_path, class: 'navbar-brand' %>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><%= link_to 'Home', root_path %></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
<%= yield %>
</div>
[...]
Now create the controller for static pages:
pages_controller.rb
class PagesController < ApplicationController
def index
end
end
Then the corresponding view:
views/pages/index.html.erb
<div class="jumbotron">
<div class="container">
<h1>Welcome!</h1>
<p>Sign up to get started.</p>
<p>
<%= link_to '#', class: 'btn btn-primary btn-lg' do %>
Sign Up »
<% end %>
</p>
</div>
and route:
config/routes.rb
[...]
root to: 'pages#index'
[...]
When you are done, proceed to the next step and let’s integrate AuthLogic together!
Setting Up AuthLogic
Models
Using AuthLogic involves two models: a basic one that we are going to call User
and a “special” one, inherited from Authlogic::Session::Base
that will be called UserSession
(you can read this section to get a quick idea of how this solution works).
First of all, create a new migration:
$ rails g model User email:string crypted_password:string password_salt:string persistence_token:string
(Here is an example migration with all possible fields)
Add the following line to the generated migration:
migrations/xxx_create_users.rb
[...]
add_index :users, :email, unique: true
[...]
Apply the migration:
$ rake db:migrate
The email
, crypted_password
, and password_salt
fields are actually optional. AuthLogic does not really care how you are authenticate – use LDAP or OAuth 2, for example. Here is the list of AuthLogic “add-ons” for supported authentication methods, however many of them are not actively maintained.
By the way, you could also have a login
field that AuthLogic will use instead of email
.
The persistence_token
field is required. AuthLogic uses it to persist the user’s session in a secure way.
Now tweak the model:
models/user.rb
[...]
acts_as_authentic
[...]
This equips the User
model with AuthLogic functionality. acts_as_authentic
accepts a block to redefine default settings (like password validation rules, for example – we will discuss it in the last section of the article).
Now create a new, special model:
models/user_session.rb
class UserSession < Authlogic::Session::Base
end
Note for Windows Users
There is a pretty serious error that Windows users are likely to encounter when using AuthLogic with Ruby 2.1 or 2.2 (I have not tested with Ruby 1.9). Full discussion can be found on GitHub, but, in short, this error is related to SCrypt, a gem that implements secure password hashing algorithm. AuthLogic uses SCrypt as a default crypto provider, but on Windows it constantly returns segmentation error and the server crashes.
The quickest way is to use another provider – AuthLogic offers a handful of them.
In this demo I will stick to SHA512, so tweak the model:
models/user.rb
[...]
acts_as_authentic do |c|
c.crypto_provider = Authlogic::CryptoProviders::Sha512
end
[...]
Controllers and Helpers
Let’s take care of the controller to manage users:
users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(users_params)
if @user.save
flash[:success] = "Account registered!"
redirect_to root_path
else
render :new
end
end
private
def users_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
This is a really basic controller that will be responsible for users’ registrations. Make sure to permit both
password
and password_confirmation
attributes, because, by default, AuthLogic checks if those two match.
Here’s the controller to manage logging in and out:
user_sessions_controller.rb
class UserSessionsController < ApplicationController
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(user_session_params)
if @user_session.save
flash[:success] = "Welcome back!"
redirect_to root_path
else
render :new
end
end
def destroy
current_user_session.destroy
flash[:success] = "Goodbye!"
redirect_to root_path
end
private
def user_session_params
params.require(:user_session).permit(:email, :password, :remember_me)
end
end
As you can see, we use the UserSession
model to authenticate the user. It persists the user’s session automatically, so the controller is really clean and simple.
What about the current_user_session
? It is a helper method:
application_controller.rb
[...]
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.user
end
helper_method :current_user_session, :current_user
[...]
UserSession.find
automatically uses persistence_token
to find the current session.
I’ve also added current_user
to easily fetch current user’s record based on the current session.
Routes
We have to set up some routes:
config/routes.rb
[...]
resources :users, only: [:new, :create]
resources :user_sessions, only: [:create, :destroy]
delete '/sign_out', to: 'user_sessions#destroy', as: :sign_out
get '/sign_in', to: 'user_sessions#new', as: :sign_in
[...]
Views
Lastly, let’s deal with the views.
First of all, the layout:
views/layouts/application.html.erb
[...]
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<%= link_to 'Logical', root_path, class: 'navbar-brand' %>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><%= link_to 'Home', root_path %></li>
</ul>
<ul class="nav navbar-nav pull-right">
<% if current_user %>
<li><span><%= current_user.email %></span></li>
<li><%= link_to 'Sign Out', sign_out_path, method: :delete %></li>
<% else %>
<li><%= link_to 'Sign In', sign_in_path %></li>
<% end %>
</ul>
</div>
</div>
</nav>
[...]
Here, I simply populate the top menu.
Now the “Register” page:
views/users/new.html.erb
<div class="page-header"><h1>Register</h1></div>
<%= form_for @user do |f| %>
<%= render 'shared/errors', object: @user %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
</div>
<%= f.submit 'Register', class: 'btn btn-primary btn-lg' %>
<% end %>
AuthLogic automatically validates that the e-mail is valid and that the passwords match and are at least 4 characters long. You can redefine those settings easily inside the model/user.rb – we will discuss that in a bit.
Add the shared partial to display errors:
views/shared/_errors.html.erb
<% if object.errors.any? %>
<div class="panel panel-warning errors">
<div class="panel-heading">
<h5><i class="glyphicon glyphicon-exclamation-sign"></i> Found errors while saving</h5>
</div>
<ul class="panel-body">
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Don’t forget to add a link to the “Register” page inside index.html.erb:
views/pages/index.html.erb
<div class="jumbotron">
<div class="container">
<h1>Welcome!</h1>
<p>Sign up to get started.</p>
<p>
<%= link_to new_user_path, class: 'btn btn-primary btn-lg' do %>
Sign Up »
<% end %>
</p>
</div>
</div>
And, lastly, code for the “Sign In” view:
views/user_sessions/new.html.erb
<div class="page-header"><h1>Sign In</h1></div>
<%= form_for @user_session do |f| %>
<%= render 'shared/errors', object: @user_session %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :remember_me %>
<%= f.check_box :remember_me %>
</div>
<%= f.submit "Log in!", class: 'btn btn-primary btn-lg' %>
<% end %>
At this point you are good to go – boot up the server and register your first user!
Storing Additional Info
AuthLogic supports a handful of “magic attributes” that are populated automatically. You can use them to store additional information about a user, like last login date or last used IP.
Go ahead and create a new migration:
$ rails g migration add_magic_columns_to_users
Tweak it like so:
xxx_add_magic_columns_to_users.rb
class AddMagicColumnsToUsers < ActiveRecord::Migration
def change
add_column :users, :login_count, :integer, :null => false, :default => 0
add_column :users, :failed_login_count, :integer, :null => false, :default => 0
add_column :users, :last_request_at, :datetime
add_column :users, :current_login_at, :datetime
add_column :users, :last_login_at, :datetime
add_column :users, :current_login_ip, :string
add_column :users, :last_login_ip, :string
end
end
and run the migration:
$ rake db:migrate
Now, for simplicity, let’s display all this information on the main page if the user is logged in:
views/pages/index.html.erb
<% if current_user %>
<div class="page-header"><h1>Welcome back!</h1></div>
<h2>Some info about you...</h2>
<div class="well well-lg">
<ul>
<li>Login count: <%= current_user.login_count %></li>
<li>Failed login count: <%= current_user.failed_login_count %></li>
<li>Last request: <%= current_user.last_request_at %></li>
<li>Current login at: <%= current_user.current_login_at %></li>
<li>Last login at: <%= current_user.last_login_at %></li>
<li>Current login IP: <%= current_user.current_login_ip %></li>
<li>Last login IP: <%= current_user.last_login_ip %></li>
</ul>
</div>
<% else %>
[...]
<% end %>
Log in and observe the result. This may come in handy if you wish to check, for example, how often your users visit a website.
Resetting a Password
Users tend to forget their passwords, therefore it is crucial to present them with a way to reset it. AuthLogic provides you with a tool to add this functionality, as well.
The author of AuthLogic suggests using a simple mechanism where a user first enters an e-mail, then receives a link to update the password, and then follows the link to actually set the new password. This link contains a special “perishable” token that has to be reset.
Therefore we need to add a new field called perishable_token
to the users
table. Note that we do not call it something like reset_password_token
. AuthLogic does not dictate that this token can be used only for password resetting – you may use it, for example, to activate users’ accounts.
Apart from perishable_token
, AuthLogic also supports single_access_token
that is ideal for APIs – it provides access, but does not persist. Read more here.
Okay, so create and apply a new migration:
$ rails g migration add_perishable_token_to_users perishable_token:string
$ rake db:migrate
Obviously we need a special controller to manage password resets and a mailer.
Start with controller:
password_resets_controller.rb
class PasswordResetsController < ApplicationController
def new
end
def create
@user = User.find_by_email(params[:email])
if @user
@user.deliver_password_reset_instructions!
flash[:success] = "Instructions to reset your password have been emailed to you."
redirect_to root_path
else
flash[:warning] = "No user was found with that email address"
render :new
end
end
def edit
@user = User.find_by(perishable_token: params[:id])
end
def update
@user = User.find_by(perishable_token: params[:id])
if @user.update_attributes(password_reset_params)
flash[:success] = "Password successfully updated!"
redirect_to root_path
else
render :edit
end
end
private
def password_reset_params
params.require(:user).permit(:password, :password_confirmation)
end
end
new
is the action that will be called when a user clicks on the “Forgot your password?” link.
create
processes the sent data and tries to fetch a user by e-mail. If this user is found, password reset instructions are sent to their e-mail.
edit
is called when a user visits the link that was sent to the provided e-mail. This link contains our perishable token that is employed to find the user record. The edit page contains another form to enter a new password.
Inside the update
action, fetch the user and update their password.
We’ll need the routes, as well:
config/routes.rb
...
resources :password_resets, only: [:new, :create, :edit, :update]
...
Now, add a new method to your model:
models/user.rb
[...]
def deliver_password_reset_instructions!
reset_perishable_token!
PasswordResetMailer.reset_email(self).deliver_now
end
[...]
reset_perishable_token!
is a method supplied by AuthLogic – it simply sets perishable_token
to a new random value and saves the record.
We also have to create our new mailer:
mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout 'mailer'
end
mailers/password_reset_mailer.rb
class PasswordResetMailer < ApplicationMailer
def reset_email(user)
@user = user
mail(to: @user.email, subject: 'Password reset instructions')
end
end
views/layouts/mailer.text.erb
<%= yield %>
views/password_reset_mailer/reset_email.text.erb
Here is your link to reset password: <%= edit_password_reset_url(@user.perishable_token) %>.
Also don’t forget to set the default URL options:
config/environments/development.rb
config.action_mailer.default_url_options = { host: '127.0.0.1:3000' }
Please note that in the development environment, e-mails won’t actually be sent, but you’ll be able to see their contents inside the console. Also note that my demo app won’t send e-mails, but it’s easy to setup for a production environment. Read more here.
Lastly, update the views to include the “Forgot your password?” link:
views/user_sessions/new.html.erb
<div class="page-header"><h1>Sign In</h1></div>
<%= form_for @user_session do |f| %>
[...]
<%= f.submit "Log in!", class: 'btn btn-primary btn-lg' %>
<br/><br/>
<%= link_to 'Forgot your password?', new_password_reset_path, class: 'btn btn-sm btn-default' %>
<% end %>
Now go ahead and check how this is working!
Tweaking the Default Settings
As I already told you, AuthLogic provides some default settings that you can easily change by passing a block to acts_as_authentic
. To learn more about those settings, you can browse the documentation, but let me highlight some of them.
Brute Force
By default, AuthLogic tries to prevent brute force attacks (when someone is using a program to “guess” a password simply by using various combinations of symbols), but only if the failed_login_count
field is present in your table. There are two settings that you can change:
consecutive_failed_logins_limit
(default is 50) – allowed number of consecutive failed logins. Set to0
to disable brute force protection.failed_login_ban_for
(default is 2 hours) – how long the user’s account will be locked out. Set to0
to lock out permanently.
More can be read here.
HTTP Basic Auth
AuthLogic supports HTTP basic authentication, which is enabled by default.
Password Configuration
There are various settings that can be used to change the default fields to store login and password information, as well as changing the method to find the user’s session.
There is also a page that lists settings related to password’s validation rules and password confirmation.
Logout on Timeout
You can also instruct AuthLogic to mark the user as logged out after a certain period of time. Just set logout_on_timeout
to true
and use stale?
to check if the user needs to log back in.
Read more here.
You can also use the logged_in_timeout
setting to determine when a user is logged in or not. More can be found here.
E-mail Configuration
Visit this page to learn about various options related to e-mails (field to store e-mail, validation rules, and more).
There are many more options that you can use for AuthLogic customization, so be sure to browse the documentation.
Conclusion
In this article, we’ve taken a look at AuthLogic and built a simple demo app. It is really interesting to observe the various authentication solutions and compare their philosophies, isn’t it?
Which authentication solution do you prefer and why? Have you encountered any limitations or special cases that required you to develop your own authentication system? Share your experience!
Feedback is welcome, as always. Thanks for staying with me and see you soon.
Frequently Asked Questions (FAQs) about Rails Authentication with Authlogic
How do I install Authlogic in my Rails application?
To install Authlogic in your Rails application, you first need to add the gem to your Gemfile. Open your Gemfile and add the following line: gem 'authlogic'
. Then, run bundle install
to install the gem. After the gem is installed, you can use it in your application by including it in your User model with acts_as_authentic
.
How do I create a new session in Authlogic?
Creating a new session in Authlogic is straightforward. First, you need to create a new Session model. This can be done by running rails generate model UserSession
. Then, in your controller, you can create a new session by calling UserSession.new(params[:user_session])
. This will create a new session with the parameters passed from the form.
How do I authenticate a user in Authlogic?
Authenticating a user in Authlogic is done by calling the save
method on a UserSession instance. If the session is valid, the user will be logged in and the method will return true. If the session is not valid, the method will return false and the user will not be logged in.
How do I log out a user in Authlogic?
Logging out a user in Authlogic is done by calling the destroy
method on the current UserSession instance. This will end the session and log out the user.
How do I check if a user is logged in Authlogic?
You can check if a user is logged in Authlogic by calling the UserSession.find
method. If a session is found, it means that a user is logged in. If no session is found, it means that no user is logged in.
How do I handle password encryption in Authlogic?
Authlogic handles password encryption automatically. When you call acts_as_authentic
in your User model, Authlogic will automatically encrypt the password using a secure hash function. You don’t need to worry about handling password encryption yourself.
How do I customize the validation messages in Authlogic?
You can customize the validation messages in Authlogic by overriding the default error messages in your User model. For example, you can change the message for a blank password by adding validates_presence_of :password, message: 'Your custom message'
to your User model.
How do I use Authlogic with Rails API?
You can use Authlogic with Rails API by including the Authlogic module in your ApplicationController and calling acts_as_authentic
in your User model. Then, you can use the UserSession
model to handle authentication in your API endpoints.
How do I handle session timeout in Authlogic?
Authlogic provides a logout_on_timeout
configuration option that you can use to automatically log out users after a certain period of inactivity. You can set this option in your UserSession model.
How do I handle multiple sessions in Authlogic?
Authlogic does not support multiple sessions out of the box. However, you can implement this feature yourself by creating a separate Session model for each type of session you want to support. Then, you can use these models to handle authentication for different types of users.
Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.