ruby on rails - How to customize bcrypt? -
i have built working api using rails using this pluralsight blog. in app user verification i've used gems bcrypt
, jwt
. using mysql database , have user table named users
.
which looks this:
saw production database looks this:
since have done quite research , found out while using latest version of bcrypt
must have password_digest
column, cant store encrypted password , password salt separately.
problem can't change schema of existing table. kindly let me know if there's work around? or there exists better approach tackle issue.
it is possible , isn't difficult. i'll show step-by-step procedure helps understand how that.
how passwords work under hood
we start locating has_secure_password
. it's defined in activemodel::securepassword::classmethods
. activemodel::securepassword
active model concern responsible passwords. it's included activerecord::base
.
if at source code can see includes instancemethodsonactivation
. defines 4 instance methods:
authenticate
- buildbcrypt::password
based on value returnedpassword_digest
(normally column accessor) , tests whether matchesunencrypted_password
.password
- ordinary attribute reader.password=
- custom attribute setter. converts assignmentpassword
assignmentpassword_digest
. remember in ruby these "assignments" method calls. in other words, method translates call#password=
call#password_digest=
.password_confirmation=
- ordinary attribute writer.
based on above code, can see model needs implement 2 methods compatible rails secure passwords. these methods #password_digest
, #password_digest=
. activemodel::securepassword
doesn't care whether backed database columns! perspective, ordinary ruby methods.
implementation
we know #password_digest
needs assemble bcrypt digest based on 2 columns in our database. conversely, #password_digest=
needs disassemble bcrypt digest salt , checksum.
let's write simple test case ensure code works. our goal make test pass.
require 'test_helper' class usertest < activesupport::testcase name = 'gregnavis'.freeze password = '1234abcd'.freeze test 'passwords work' # first, store user in database check fields # serialized correctly. user.create!(name: name, password: password, password_confirmation: password) # second, reload model ensure works if reset # state isn't serialized database. user = user.find_by_name!(name) # third, let's see whether authentication works. assert(user.authenticate(password)) assert_not(user.authenticate("#{password}1")) end end
looking @ source code of #password=
can see method receives instance of bcrypt::password
argument. the docs bcrypt::password
list salt
(which includes salt, version, , cost) , checksum
attributes. means following implementation should pass test:
class user < activerecord::base has_secure_password def password_digest # need glue 2 parts. "#{password_salt}#{password_hash}" end def password_digest=(digest) # need split bcrypt::password 2 parts. self.password_salt = digest.salt self.password_hash = digest.checksum end end
that's it! test green. confirmed joining digest.salt
, digest.checksum
gives complete hash. did in console.
Comments
Post a Comment