Ruby on Rails with Bcrypt Auth
Summary: This will be a guide on how to use Bcrypt authentication for Ruby on Rails projects. I will also identify easy mistakes to make so that you don’t make them later!
Assuming that a Ruby on Rails project has already been created, make sure that the Bcrypt gem is included in the project Gemfile and run bundle install
. You can verify that Bcrypt has been installed properly by checking the Gemfile.lock for bcrypt and its version.
#=> Gemfile
gem 'bcrypt', '~> 3.1.7'
For the implementation of Bcrypt we will be creating a User model with a username and password which we want to hash for protection. For convenience I will use a generator to create the model:
> rails g model user username:string password_digest:string
...
invoke active_record
create db/migrate/20231021034145_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
Make sure that your user has password_digest
, this is important because Bcrypt requires an attribute with XXXX_digest
. See here for the documentation and some more information. Next, go ahead and run the migrations: rails db:migrate
, verify that the migration was performed correctly by checking app/db/schema.rb.
#=> app/db/schema.rb
ActiveRecord::Schema.define(version: 2023_10_21_034145) do
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
Now that we have confirmed the User model has the password_digest
attribute we need to add a line of code to app/models/user.rb which was created with the model generator we used earlier. The piece of code that requires the ‘_digest’ attribute is the method has_secure_password
from Bcrypt. Let’s go ahead and add that method to the User model.
#=> app/models/user.rb
class User < ApplicationRecord
has_secure_password
end
This method adds some validations and other features that can be very convenient to take advantage of. To test what we have created I will just be using the rails console out of convenience since if it works in the console it will also be applied to views and etc. if the same process is followed. Go ahead and run the rails console with rails c
, then create a new User.
> User.create(
username: 'Michael Scott',
password: 'gabagool',
password_confirmation: 'gabagool'
)
=> [
["username", "Michael Scott"],
["password_digest", "$2a$12$UvF2l.oge7YF/u80BQTT6ORCd6KvRHkY3UZ0RwAPlaUgqVJRlXHem"],
["created_at", "2023-10-21 04:01:24.330928"],
["updated_at", "2023-10-21 04:01:24.330928"]
]
>
> User.create(
username: 'Dwight Schrute',
password_digest: 'beets'
)
=> [
["username", "Dwight Schrute"],
["password_digest", "beets"],
["created_at", "2023-10-21 04:28:28.329280"],
["updated_at", "2023-10-21 04:28:28.329280"]
]
Notice that in the ‘Michael Scott’ model parameters we enter the password simply with the password
parameter and not password_digest
like we do in the ‘Dwight Schrute’ model. In the ‘Michael Scott’ object the password has been hashed with Bcrypt in the password_digest
attribute, but you can see that the other password_digest
only contains ‘beets’. Go ahead and check that this works in the console with the authenticate method.
> User.find_by_username('Michael Scott').authenticate('gabagool')
=> [
["username", "Michael Scott"],
["password_digest", "$2a$12$UvF2l.oge7YF/u80BQTT6ORCd6KvRHkY3UZ0RwAPlaUgqVJRlXHem"],
["created_at", "2023-10-21 04:01:24.330928"],
["updated_at", "2023-10-21 04:01:24.330928"]
]
>
> User.find_by_username('Michael Scott').authenticate('baby_back_ribs')
=> false
>
> User.find_by_username('Dwight Schrute').authenticate('beets')
=> BCrypt::Errors::InvalidHash (invalid hash)
In the above section from the terminal, notice that the authenticate method returns the OBJECT that the method is being called on if it is the correct password, but if the password is incorrect, it will return the Boolean value FALSE. The last command in the above code shows that by assigning the value ‘beets’ directly to password_digest
Bcrypt gets completely ignored and returns an error since it wasn’t hashed.
ASIDE: For those who have stuck around, the image at the top was created by Bing with the prompt “Generate a photo based on ruby on rails with halloween and gothic undertones” which I thought of since Halloween is coming up. I like how it created the girl, Ruby, on train tracks, rails, with almost like a Melanie Martinez aesthetic. Anyways, totally random but thought it was fun.
Hopefully this helps someone with implementing Bcrypt with Ruby on Rails or solving an issue I may have mentioned above.