Password hashing

node.bcrypt.js is a Lib to help you hash passwords.

bycrpt is a popular key derivation function for hashing password. Its is currently believed to be secure and it's use is considered a best practice when saving a User's password.

In this section we will cover the hashing and verifying of a User's password. Essentially these two methods are simply wrappers around the node.bcrypt.js library.

  1. Add node.bcrypt.js to your package.json

    package.json

       "bcrypt": "0.7.x",
    
  2. Updated your dependencies

     npm install
    
  3. Write a test for creating a password hash:

    test/user/models.js

     describe('Users: models', function () {
    
       // ... Previous test
    
       describe('#hashPassoword()', function () {
         it('should return a hashed password asynchronously', function (done) {
    
           var password = 'secret';
    
           User.hashPassword(password, function (err, passwordHash) {
             // Confirm that that an error does not exist
             should.not.exist(err);
             // Confirm that the passwordHash is not null
             should.exist(passwordHash);
             done();
           });
         });
       });
    
     });
    

    We have not created the hashPassoword function, yet, so the test will fail:

     addition
       ✓ should add 1+1 correctly
    
     Users: models
       #create()
         ✓ should create a new User
       #hashPassoword()
         1) should return a hashed password asynchronously
    
     ✖ 1 of 3 tests failed:
    
     1) Users: models #hashPassoword() should return a hashed password asynchronously:
        TypeError: Object function model(doc, fields, skipId) {
       if (!(this instanceof model))
         return new model(doc, fields, skipId);
       Model.call(this, doc, fields, skipId);
     } has no method 'hashPassword'
    
    
  4. Create the hashPassword function:

    Mongoose allows us to add static constructor methods to Models by add the function to the statics object of the userSchema.

    user/models.js

     //...
     var bcrypt = require('bcrypt');
    
     // Constants
     var BCRYPT_COST = 12;
    
     //...
    
     userSchema.statics.hashPassword = function (passwordRaw, fn) {
       // To speed up tests, we do a NODE_ENV check.
       // If we are in the test environment we set the BCRYPT_COST = 1
       if (process.env.NODE_ENV === 'test') {
         BCRYPT_COST = 1;
       }
       // encrypt the password using bcrypt; pass the callback function
       // `fn` to bcrypt.hash()
       bcrypt.hash(passwordRaw, BCRYPT_COST, fn);
     };
    

    Tests are now passing:

     addition
       ✓ should add 1+1 correctly
    
     Users: models
       #create()
         ✓ should create a new User
       #hashPassoword()
         ✓ should return a hashed password asynchronously
    
     ✔ 3 tests complete (55 ms)
    
  5. Write a test for comparing a password and a hash:

    test/user/models.js

     describe('#comparePasswordAndHash()', function () {
       it('should return true if password is valid', function (done) {
    
         var password = 'secret';
    
         // first we need to create a password hash
         User.hashPassword(password, function (err, passwordHash) {
           // Confirm that that an error does not exist
           User.comparePasswordAndHash(password, passwordHash, function (err, areEqual) {
             // Confirm that that an error does not exist
             should.not.exist(err);
             // Confirm that the areEqaul is `true`
             areEqual.should.equal(true);
             // notice how we call done() from the final callback
             done();
           });
         });
       });
    
       it('should return false if password is invalid', function (done) {
    
         var password = 'secret';
    
         // first we need to create a password hash
         User.hashPassword(password, function (err, passwordHash) {
    
           var fakePassword = 'imahacker';
    
           // Confirm that that an error does not exist
           User.comparePasswordAndHash(fakePassword, passwordHash, function (err, areEqual) {
             // Confirm that that an error does not exist
             should.not.exist(err);
             // Confirm that the are Eqaul is `false`
             areEqual.should.equal(false);
             done();
           });
         });
       });
     });
    

    We have not created the comparePasswordAndHash function, yet, so the test will fail:

    note: you may need to restart the mocha test runner npm test

     addition
       ✓ should add 1+1 correctly
    
     Users: models
       #create()
         ✓ should create a new User
       #hashPassoword()
         ✓ should return a hashed password asynchronously
       #comparePasswordAndHash()
         1) should return true if password is valid
         2) should return false if password is invalid
    
     ✖ 2 of 5 tests failed:
    
  6. Create the comparePasswordAndHash function:

    user/models.js

     //...
     userSchema.statics.comparePasswordAndHash = function (password, passwordHash, fn) {
       // compare the password to the passwordHash
       bcrypt.compare(password, passwordHash, fn);
     };
    

    Restart the test runner, test are now passing:

     addition
       ✓ should add 1+1 correctly
    
     Users: models
       #create()
         ✓ should create a new User
       #hashPassoword()
         ✓ should return a hashed password asynchronously
       #comparePasswordAndHash()
         ✓ should return true if password is valid
         ✓ should return false if password is invalid
    
     ✔ 5 tests complete (96 ms)
    

Resources

Comments