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