Password Encryption Using bcrypt, Sequelize and Node.js

This post talks about how to encrypt the user password using Sequelize and store in PostgreSQL.

Architecture Diagram ( IMG 1 )Architecture Diagram ( IMG 1 )

Install via NPM

npm install bcrypt
npm install sequelize

Usage with Sequelize Model

const bcrypt = require('bcrypt');

var userSchema = sequelize.define("users", {
 userId: {
  field: 'user_id',
  autoIncrement: true,
  primaryKey: true,
  type: Sequelize.INTEGER
},
 password: {
  field: 'user_password',
  type: Sequelize.STRING,
  allowNull: true
 },
 name: {
  type: Sequelize.STRING,
  field: 'user_name',
  allowNull: false
 },
 email: {
  type: Sequelize.STRING,
  field: 'user_email',
  allowNull: false
 },
},
{
 hooks: {
  beforeCreate: async (user) => {
   if (user.password) {
    const salt = await bcrypt.genSaltSync(10, 'a');
    user.password = bcrypt.hashSync(user.password, salt);
   }
  },
  beforeUpdate:async (user) => {
   if (user.password) {
    const salt = await bcrypt.genSaltSync(10, 'a');
    user.password = bcrypt.hashSync(user.password, salt);
   }
  }
 },
 instanceMethods: {
  validPassword: (password) => {
   return bcrypt.compareSync(password, this.password);
  }
 }
});
userSchema.prototype.validPassword = async (password, hash) => {
 return await bcrypt.compareSync(password, hash);
}
 return userSchema;
}

Validate Password

const authenticateUserWithemail = (user) => {
 return new Promise((resolve, reject) => {
  try {
   usermodel.findOne({
   where: {
    user_email: user.userName // user email
   }
   }).then(async (response) => {
    if (!response) {
     resolve(false);
    } else {
      if (!response.dataValues.password ||
       !await response.validPassword(user.password,
        response.dataValues.password)) {
         resolve(false);
      } else {
       resolve(response.dataValues)
      }
     }
    })
   } catch (error) {
   const response = {
    status: 500,
    data: {},
   error: {
    message: "user match failed"
   }
   };
  reject(response);
  }
 })
}

Sequelize Model

Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends Model.

The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types).

A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as User) while tables have pluralized names (such as Users), although this is fully configurable.

Sequelize hooks

Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a beforeUpdate hook.

Note: You can't use hooks with instances. Hooks are used with models.

beforeCreate

Initialized before entity creation.

beforeUpdate

Initialized before entity updation.

Hooks firing order

The diagram below shows the firing order for the most common hooks.

Note: this list is not exhaustive.

(1)
  beforeBulkCreate(instances, options)
  beforeBulkDestroy(options)
  beforeBulkUpdate(options)
(2)
  beforeValidate(instance, options)

[... validation happens ...]

(3)
  afterValidate(instance, options)
  validationFailed(instance, options, error)
(4)
  beforeCreate(instance, options)
  beforeDestroy(instance, options)
  beforeUpdate(instance, options)
  beforeSave(instance, options)
  beforeUpsert(values, options)

[... creation/update/destruction happens ...]

(5)
  afterCreate(instance, options)
  afterDestroy(instance, options)
  afterUpdate(instance, options)
  afterSave(instance, options)
  afterUpsert(created, options)
(6)
  afterBulkCreate(instances, options)
  afterBulkDestroy(options)
  afterBulkUpdate(options)

instanceMethods

Instance methods are methods that are available on instances of the model. We often write these to get information or do something related to that instance.

Definition

const Pug = db.define('pugs', {*/* etc*/*})

// instance methods are defined on the model's .prototype
Pug.prototype.celebrateBirthday = function () {
  // 'this' in an instance method refers to the instance itself
  const birthday = new Date(this.birthday)
  const today = new Date()
  if (birthday.getMonth() === today.getMonth() && today.getDate() === birthday.getDate()) {
    console.log('Happy birthday!')
  }
}

Usage

const createdPug = await Pug.create({name: 'Cody'}) // let's say `birthday` defaults to today
// the instance method is invoked *on the instance*
createdPug.celebrateBirthday() // Happy birthday!

Class Methods

Class methods are methods that are available on the model itself (aka the class). We often write these to get instances, or do something to more than one instance.

Definition

const Pug = db.define('pugs', {*/* etc*/*})

// class methods are defined right on the model
Pug.findPuppies = function () {
  // 'this' refers directly back to the model (the capital "P" Pug)
  return this.findAll({ // could also be Pug.findAll
    where: {
      age: {$lte: 1} // find all pugs where age is less than or equal to 1
    }
  })
}

Usage

const* foundPuppies = await Pug.findPuppies()
console.log('Here are the pups: ', foundPuppies)

bcrypt

Is a password-hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

The bcrypt function is the default password hash algorithm for OpenBSD and other systems including some Linux distributions such as SUSE Linux.

bcrypy API

genSaltSync(rounds, minor) rounds — [OPTIONAL] — the cost of processing the data. (default — 10)

minor — [OPTIONAL] — minor version of bcrypt to use. (default — b)

genSalt(rounds, minor, cb)

rounds — [OPTIONAL] — the cost of processing the data. (default — 10)

minor — [OPTIONAL] — minor version of bcrypt to use. (default — b)

cb — [OPTIONAL] — a callback to be fired once the salt has been generated. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.

err — First parameter to the callback detailing any errors.

salt — Second parameter to the callback providing the generated salt.

hashSync(data, salt)

data — [REQUIRED] — the data to be encrypted.

salt — [REQUIRED] — the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under Usage).

hash(data, salt, cb)

data — [REQUIRED] — the data to be encrypted.

salt— [REQUIRED] — the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under Usage).

cb — [OPTIONAL] — a callback to be fired once the data has been encrypted. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.

err — First parameter to the callback detailing any errors.

encrypted — Second parameter to the callback providing the encrypted form.

compareSync(data, encrypted)

data — [REQUIRED] — data to compare.

encrypted — [REQUIRED] — data to be compared to.

compare(data, encrypted, cb)

data — [REQUIRED] — data to compare.

encrypted — [REQUIRED] — data to be compared to.

cb — [OPTIONAL] — a callback to be fired once the data has been compared. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.

err — First parameter to the callback detailing any errors.

same — Second parameter to the callback providing whether the data and encrypted forms match [true | false].

getRounds(encrypted) — return the number of rounds used to encrypt a given hash.

encrypted — [REQUIRED] — hash from which the number of rounds used should be extracted.

That's it for this post. Hope you found it helpful and thank you for reading.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics