Ruby
Article

Rails Authentication with Authlogic

By Ilya Bodrov-Krukowski

Authentication in Rails

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 &raquo;
    <% 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 &raquo;
      <% 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 to 0 to disable brute force protection.
  • failed_login_ban_for (default is 2 hours) – how long the user’s account will be locked out. Set to 0 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.

  • ben

    Hello, The tutorial was coming along just fine up to the point when we got to Password reset. Adding the new_password_rest_path in the user_sessions new.html.erb results in the following error ” undefined local variable or method `new_password_reset_path'” SO I went ahead and added the password_resets resources. That got rid of the error but that got me wondering how do you route correctly to display the /reset_email.text.erb file?

  • http://www.babar.im/ Babar Al-Amin

    If there anything like `authenticate_user!` from devise?
    How do I protect my private area?

  • Rich

    Is there a way to only allow access to certain pages by users who are logged in?

    • Ilya Bodrov

      See my answer to Al-Amin below. This is a bare-bones solution and developers have to code such method manually.

      • Rich

        I do have one question:
        in your tutorial you had us change ‘user.rb’ the first time adding acts_as_authentic
        then we modified it again with

        acts_as_authentic do |c|

        c.crypto_provider = Authlogic::CryptoProviders::Sha512

        end

        does this mean that it should look like this?

        class User < ActiveRecord::Base

        acts_as_authentic

        acts_as_authentic do |c|

        c.crypto_provider = Authlogic::CryptoProviders::Sha512

        end

        end

        • Ilya Bodrov

          No, of course not. `acts_as_authentic` accepts a block with configuration and it should present only one time in your model. Probably, I should’ve stated it more clear

          • Rich

            One other problem:
            I switched email to just name to see how things hook up. This of course changed f.email_field to f.text_field.
            But in the sign in page I am getting this error

            undefined method `name’ for #

  • Peter Stavling

    Well explained! However, I get the error

    Authlogic::Session::Activation::NotActivatedError (You must activate the Authlogic::Session::Base.controller with a controller object before creating objects)

    Have any idea what is missing?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.