Handmade Rails Authentication

Update: As of rails4 I still find the below mentioned session_countdown useful, but auto_hash has been replace with the rails native has_secure_password

Although there is an abundance of rails authentication/login plugins (Clearance, Devise/Warden, Authlogic, Simplest Auth, Restful Authentication, etc) I still find myself unsatisfied and therefore creating custom systems. If the abundance of authentication plugins hints at anything, it’s that many others feel the same way.

I have many reasons/rationalizations for wanting to do this. I often find these existing plugins to be too invasive; involving themselves in models, controllers, tasks, mailers, and I can’t imagine what else.

I also often find myself with unique authentication requirements, such as needing both a normal login form as well as a AJAX type interface, in the same application. Even situations where the password is optional.

I’m sure many of the existing authentication plugins can do many things when pushed, but then I find myself trying to bend opinionated software to my will, which honestly, I’ve had my fill of, being a rails developer.

Handmade authentication is not that difficult, and with the few simple plugins that I’ve created, its almost trivial. Below I show how to build an authentication system in rails 3.x with my new gems session_countdown and auto_hash.

I’m going to comment the code below with “SC” and “AH” to make it easy to spot where these two plugins are used.

You can view the docs and source for these gems at github:

http://github.com/kswope/session_countdown

http://github.com/kswope/auto_hash

Below are the steps to create a Ruby on Rails 3.x application that demonstrates how to use these plugins to create an authenticated website.

Lets get started


rails new handmade_authentication_demo

In Gemfile

gem "auto_hash"
gem "session_countdown"

Install the gems

bundle install

Generate the user model

shell> rails generate model user

In db/migrate/*_create_users.rb

class CreateUsers < ActiveRecord::Migration

  def self.up
    create_table :users do |t|
      t.string :email
      t.string :password
      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

In app/models/user.rb ( this is one of auto_hash’s only two appearances, the other being password_hash_match?() )

class User < ActiveRecord::Base

  auto_hash :password # AH

end
rake db:migrate

Create a new user from the console (don’t use seeds.rb, doesn’t run model hooks)

irb> u = User.new
irb> u.email = "adrian@example.com"
irb> u.password = "RamessesII"
irb> u.save


In config/routes.rb


root :to => "public#index"

match "login_screen",
      :to => "public#login_screen",
      :as => "login_screen"

match "login",
      :to => "public#login",
      :as => "login"

match "logout",
      :to => "public#logout",
      :as => "logout"

match "count",
      :to => "public#count",
      :as => "count"


Don’t forget to delete index.html, as I’m sure everybody does.

shell> rm public/index.html


We are just going use one controller for this simple demo:

shell> rails generate controller public

Add this code to  app/controllers/public_controller.rb


  skip_before_filter :authorize, :except => [:index]

  def login

    user = User.find_by_email params[:email]

    if user && user.password == params[:password] # AH
      session.countdown_start(1.minute) # SC
      flash[:notice] = "Success logging in"
      redirect_to session[:original_uri] || :root
    else
      flash[:notice] = "Email/Password wrong"
      render :login_screen
    end

  end

  def logout
    session.countdown_abort # SC
    flash[:notice] = "Logged out"
    redirect_to :login_screen
  end

  def count
    render :text => session.countdown_count.to_i # SC
  end


Here is app/controllers/application_controller.rb. This is what puts your application behind an authentication wall


  before_filter :authorize

  def authorize

    if session.countdown_running? # SC
      session.countdown_restart # SC
    else
      session[:original_uri] = request.fullpath
      if session.countdown_expired? # SC
        flash[:notice] = "Login Expired"
      else
        flash[:notice] = "Please login"
      end
      redirect_to :login_screen
    end
  end


Login page app/views/public/login_screen.html.erb


<p><%= flash[:notice] %></p>

<%= form_tag(:login) do %>
  <%= text_field_tag(:email, params[:email]) %>
  <%= password_field_tag(:password) %>
  <%= submit_tag("Log In") %>
<% end %>

Your index page (what’s behind the auth wall) app/views/public/index.html.erb. There’s an AJAX updater to show a countdown, demoing countdown_count().


<p><%= flash[:notice] %></p>

<span id="count"></span> seconds until login expires - refresh to "restart" countdown before it expires, or <%= link_to("logout", :logout) %>

<script type="text/javascript">
  new Ajax.PeriodicalUpdater('count', '/count', {
    frequency: 1, decay: 1
  });
</script>


Adding a “Remember me” feature

If you want an “remember me” feature you need to do two things.

1. Set timer for far future when user checks “remember me”

session.countdown_start(1.year)

2. Tell rails to serve up a persistent cookie instead of session cookie:

In rails 3x, in config/initializers/session_store.rb

ActionController::Base.session = { :expire_after => 1.year }

In rails 2x, probably in app/controllers/application_controller.rb

ActionController::Base.session_options[:expire_after] = 1.year

Persistent vs session cookies explained

There are two types of browser cookies: ones with expiration dates and ones without. When a cookie doesnt have an expiration date it’s a session cookie and will be deleted when the browser quits. If the cookie has an expiration date it’s a persistent cookie (a.k.a. domain cookie) and will be valid until that date.

“Remember me” could work fine with only session cookies, provided the user never quits the browser, but users expect “remember me” to never expire their login and to persist across browser quits. It also makes sense to set a far future expiration date or the cookie will eventually expire before the login does.

Advertisements

One thought on “Handmade Rails Authentication

  1. I applaud this approach. It’s very similar to how Restful Auth started out — a generator for a set of conventions that matched my personal preference. I hate plugins that add configuration dsl crap. You have to remember specific modules to require to change your password hashing system from sha to bcrypt. You have to add strategy modules to support various authentication schemes. Somewhere along the line, Restful Auth started doing some of that, and I quit using it 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s