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 endendThis 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) endendNeat!
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
- There is a Railscast by Ryan Bates explaining the above. So if you prefer to watch a screencast, give it a shot: Authentication in Rails 3.1
- Advanced features: Remember Me & Reset Password (by Ryan Bates, too)
Comments
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!
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.
Thank you for this.