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.
Add node.bcrypt.js to your package.json
package.json
"bcrypt": "0.7.x",
Updated your dependencies
npm install
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'
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)
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:
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)