Rails: User Authentication using has_secure_password

When I encountered Rails I was delighted by all those gems that take away the busywork of reimplementing basic modules again and again. Specifically authentication is such a dreaded module. Glance into the Ruby Toolbox and pick any solution!

In my recent project, devise was used. What shall I say? It is a pleasure in the beginning but grows more and more into pain the longer the project lasts. Customization is the hell. Yes, devise tries to be as flexible as possible. Yes, you can override each controller and view. However, if you are going to customize that much, why not implement everything yourself in the first place? That way you know exactly what happens and why and you do not have to spend hours to find out which controller to override. Ryan Bates nailed it:

Something as core to your application as authentication — I don’t really like to use engines for that kind of thing because I find I often end up writing/overriding so much of the functionality in the controller and view layers that the engines really loose their benefit and just make things overall more complicated.
– Ryan Bates in Railscast 250: Authentication from Scratch (0:43 – 1:00)

So this time, I decided to choose the manual way. And as it turns out, the basics are pretty simple using just plain rails. Basically, I followed Ryans screencast step by step. But since I am toying with rails 3.1, I wanted to try the newly introduced helper method has_secure_password. It takes care of basic password encryption so you do not have to bother with BCrypt hashing and salting.

This is Ryans Rails 3.0 solution:

class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation
  
  attr_accessor :password
  before_save :encrypt_password
  
  validates_confirmation_of :password
  validates_presence_of :password, :on => :create
  validates_presence_of :email
  validates_uniqueness_of :email
  
  def self.authenticate(email, password)
    user = find_by_email(email)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end
  
  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end
end

This is my solution using has_secure_password:

class User < ActiveRecord::Base
  has_secure_password
  
  attr_accessible :email, :password, :password_confirmation
  
  validates_presence_of :email
  validates_uniqueness_of :email
  
  def self.authenticate(email, password)
    find_by_email(email).try(:authenticate, password)
  end
end

Neat!

Have a look at the well documented implementation for further details: https://github.com/rails/rails/blob/master/activemodel/lib/active_model/secure_password.rb

Update

Comments

  • apocalyptiq says:

    I’m starting some bigger project, firstly I installed devise – on the beginning it was ok, but when I wanted to configure some more complex stuff.. I started to search for some other solution, found authlogic, configured it – it taken me a couple of days, but it was way more fexible than devise.

    but as I see now, authlogic isn’t well maintained – it have maaany active issues, and last commit from may… And as I heard on IRC, it doesn’t work with rails 3.1

    So I’m also going to sacrifice a few days more and migrate my authentication from authlogic to my own, from scrach :)

    thanks for this article!

    • eric teubert says:

      Hey apocalyptiq,

      thanks for your comment. I just added two links for digging deeper. Both are railscasts by Ryan Bates – I highly recommend them for everyone dealing with Rails.

      The first one is basically my article as a video. The more recent second one explains how to implement the frequently needed features “remember me” and “reset password” all by yourself.

  • Ed Jones says:

    Thank you for this.

  • Leave a Reply

    *