If you're here you probably already know that storing passwords in plain text is a bad idea. Here is a step-by-step guide on how to set up Bcrypt in your Rails project. It's quite simple, yet offers powerful security to your user's information as well as any sensitive data you might be storing in your database. It's so simple in fact, you'll probably have time to read about how it works at the end if you aren't already familiar.
- If you don't have your Rails project set up go ahead a run:
rails new ProjectName
- Add the 'bcrypt' Gem to your project's
Gemfile
:
gem 'bcrypt', '~> 3.1.13'
In your terminal, run:
bundle install
- Generate the model you will be storing the password in and include
password_digest
as an attribute. Remember the default type when using a generator is a string which is how thepassword_digest
will be stored.
rails g model User name email password_digest
- In the model, add your associations and validations and include
has_secure_password
class User < ApplicationRecord
has_secure_password
# Include additional validations...
end
- Migrate your database to create your
users
table
rails db:migrate
- Implement user registration in your
users_controller
with a create action along with your error handling. Though we included apassword_digest
attribute in ouruser
table, we will still take in apassword
andpassword_confirmation
in ouruser_params
. Bcrypt will do the work of hashing the plain text password and storing it aspassword_digest
thanks to that handy single line of code in step 4,has_secure_password
.
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
def create
user = User.create!(user_params)
session[:user_id] = user.id
# Include any additional successful registration steps
end
private
def user_params
params.permit(:name, :email, :password, :password_confirmation)
end
def render_record_invalid(e)
render json: {errors: e.record.errors.full_messages}, status: :unprocessable_entity
end
end
So far the code above will take in new user information as use_params
and create a new user. It will verify that the password
and password_digest
match and save the new user to the database if so along with the Bcrypt generated password_digest
. If the password and confirmation do not match, the bang operator in User.create!
will raise an error that will be rescued with the render_record_invalid function to return any errors so we can render them to the dom for our user.
- Lastly, we will authenticate our users when they sign in. This will go into the cont
roller responsible for your user authentication, such as a
sessions_controller
. First, the action will find theuser
via their email, in this example and authenticate the password they input against the storepassword_digest
.
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
# Handle successful login
else
# Handle login failure
end
end
end
Thats it! Your passwords are being stored securely.
How it works:
Bcrypt is a hashing algorithm that takes a bit of data (e.g. password) and creates a "digital fingerprint" from it. In other words 'password' becomes something like 2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
.
What makes Bcrypt more secure is that it adds "salt" to the password before hashing it. Salt is a random bit of additional data that is prepended to the password the user entered before it is hashed. This looks like turning our password into E4OAovh7rbpassword
before processing it through the hashing algorithm. This way, if two people have have the same password, the salt ensures that their password_digest
is completely different.
So how do we authenticate the user?
Bcrypt stores the password salt prepended on the password hash in the password_digest
so all we have to do is add that salt to the password the user entered upon log in and see if it matches. Yes, the stored hash does have embedded information about its computation. As seen in the example below from the Bcrypt official npm page.
$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
| | | |
| | | hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
| | |
| | salt = nOUIs5kJ7naTuTFkBy1veu
| |
| cost-factor => 10 = 2^10 rounds
|
hash-algorithm identifier => 2b = BCrypt
Read more about Bcrypt for Ruby here.
Top comments (3)
Awesome beginner article! If it’s cool I would like to provide some tips to improve the overall readability of your post, but the content itself is awesome congratulation!
1.
, it would be better to continue increasing the number on the subsequent items.Hope this is useful and congrats on your article again!
Thanks so much! Very helpful.
It's always a pleasure!