32

Following on from this question.

I feel like I'm almost there, but my incomplete understanding of async is preventing me from solving this. I'm basically trying to just hash a password using bcrypt and have decided to seperate out the hashPassword function so that I can potentially use it in other parts of the app.

hashedPassword keeps returning undefined though...

userSchema.pre('save', async function (next) {

  let user = this
  const password = user.password;

  const hashedPassword = await hashPassword(user);
  user.password = hashedPassword

  next()

})

async function hashPassword (user) {

  const password = user.password
  const saltRounds = 10;

  const hashedPassword = await bcrypt.hash(password, saltRounds, function(err, hash) {

    if (err) {
      return err;
    }

    return hash

  });

  return hashedPassword

}
Modermo
  • 1,852
  • 2
  • 25
  • 46

7 Answers7

95

await dosent wait for bcrypt.hash because bcrypt.hash does not return a promise. Use the following method, which wraps bcrypt in a promise in order to use await.

async function hashPassword (user) {

  const password = user.password
  const saltRounds = 10;

  const hashedPassword = await new Promise((resolve, reject) => {
    bcrypt.hash(password, saltRounds, function(err, hash) {
      if (err) reject(err)
      resolve(hash)
    });
  })

  return hashedPassword
}

Update:-

The library has added code to return a promise which will make the use of async/await possible, which was not available earlier. the new way of usage would be as follows.

const hashedPassword = await bcrypt.hash(password, saltRounds)
Akash Dathan
  • 4,348
  • 2
  • 24
  • 45
  • 7
    Might be worth making it clear that you're basically wrapping `bcrypt` in a promise in order to use `await`. Nice solution though! – developius Feb 15 '18 at 08:56
  • 1
    I was trying to stay away from using promises, but in this case it clearly needed to be done. Thanks for the solution – it worked :) – Modermo Feb 15 '18 at 09:13
  • 1
    @Modermo I would strongly advise using promises, simply because it's the future of async code. All the fancy new async/await stuff is pinned on top of promises, so it's definitely worthwhile wrapping your head around them. Plus, it makes writing complex async code much _much_ easier. – developius Feb 15 '18 at 09:16
  • 1
    Agreed. The async stuff, in my limited experience, is SO much nicer to work with. In this instance though, I really don't know how I could avoid using a promise because I want `hashPassword` to be it's own function. – Modermo Feb 15 '18 at 09:18
  • 1
    Absolutely, a promise is required for async/await. No other way around it because you can't do something like `async return ` in a callback (although this is something which would be really neat). – developius Feb 15 '18 at 09:20
  • There's a better solution now, supported by the doc. Please look at my answer [here](https://stackoverflow.com/a/63334789/5223309) – Shababb Karim Aug 10 '20 at 05:53
25

By default, bcrypt.hash(password,10) will return as promise. please check here

Example: Run the code,

var bcrypt= require('bcrypt');

let password = "12345";


var hashPassword = async function(){
    console.log(bcrypt.hash(password,10));
    var hashPwd = await bcrypt.hash(password,10);
    console.log(hashPwd);
}

hashPassword();

Output:

Promise { <pending> }
$2b$10$8Y5Oj329TeEh8weYpJA6EOE39AA/BXVFOEUn1YOFC.sf1chUi4H8i

When you use await inside the async function, it will wait untill it get resolved from the promise.

umar882
  • 251
  • 3
  • 4
  • how do we then retrieve the password ? – Eduards Jul 10 '20 at 20:54
  • 1
    @LV98 hashing is a one-way function and you won't be able to retrieve the password from hashed password. Also, if you run the code multiple times, you will see that the hashPwd changes as you generate new salt every time. So if you want to compare the passwords, you must use `bcrypt.compare`, as it is almost impossible to generate the same hash. – nick Dec 21 '20 at 22:46
  • @nick, I think Squirrel.98 is asking how do you/we retrieve the generated hash. I imagine you could just return hashPwd after the await statement runs... – DeltaFlyer Nov 05 '21 at 02:35
6

use The method bcrypt.hashSync(), It is Synchronous out of the box.

const hashedPassword = bcrypt.hashSync(password,saltRounds);
SLIMANI Mohammed
  • 407
  • 7
  • 10
  • 1
    hashSync() is the synchronous version of hash, so you don't need to await it. Because asynchronous one returns promise, which needs to be awaited. You can use synchronous one like const hashed = bcrypt.hashSync(password, saltRounds) – PrivateOmega Mar 01 '20 at 12:00
  • Oh, actually you are right. I didn't realize that at the moment. I have edited my response. Thank you – SLIMANI Mohammed Mar 02 '20 at 12:16
  • 2
    @PrivateOmega the docs says, if your using bcrypt on a server, async is preferred: A Note on Timing Attacks Why is async mode recommended over sync mode? If you are using bcrypt on a simple script, using the sync mode is perfectly fine. However, if you are using bcrypt on a server, the async mode is recommended. This is because the hashing done by bcrypt is CPU intensive, so the sync version will block the event loop and prevent your application from servicing any other inbound requests or events. The async version uses a thread pool which does not block the main event loop. – mike Apr 25 '20 at 22:30
  • @mike Yes ofcourse, what you wrote down is entirely correct and I know all of that. but I guess at that time, I was just answering to the question. – PrivateOmega Apr 26 '20 at 15:55
3

Hashing bcrypt asynchronously should be like this

bcrypt.hash(password, saltRounds, function(err, hash) {
  if (err) {
     throw err;
  }
  // Do whatever you like with the hash
});

If you are confused with sync and async. You need to read more about them. There are a lot of good articles out there.

CodeLover
  • 571
  • 2
  • 11
  • I think I need to understand the `bcrypt.hash` async function better to understand what is going on under the hood. – Modermo Feb 15 '18 at 04:29
  • Just went to understand the bcrypt function itself. As Akash said, indeed, `bcrypt.hash` doesn't return a promise, so I need to wrap it in one. – Modermo Feb 15 '18 at 09:12
  • This may help someone else out there https://stackoverflow.com/a/52087581/1274820 – user1274820 Aug 30 '18 at 00:18
2

You need to look here in the documentation.

Async methods that accept a callback, return a Promise when callback is not specified if Promise support is available.

So, if your function call takes in a callback then you can't use await on it since this function signature doesn't return a Promise. In order to use await you need to remove the callback function. You can also wrap it in a Promise and await on it but that's a bit overkill since the library already provides a mechanism to do so.

Code refactor:

try {
   // I removed the callbackFn argument
   const hashedPassword = await bcrypt.hash(password, saltRounds)
} catch (e) {
   console.log(e)
}
Shababb Karim
  • 3,614
  • 1
  • 22
  • 35
0
const hashedPassword = (password, salt) => {
    return new Promise((resolve, reject) => {
        bcrpyt.hash(password, salt, (err, hash) => {
            if (err) reject();
            resolve(hash);
        });
    });
};
hashedPassword('password', 10).then((passwordHash) => {
    console.log(passwordHash);
});
David Buck
  • 3,752
  • 35
  • 31
  • 35
kamula
  • 197
  • 2
  • 5
0

Had same issue... solved by assigning the Boolean value from a function:

compareHash = (password, hashedPassword) => {
if (!password || !hashedPassword) {
  return Promise.resolve(false);
}
return bcrypt.compare(password, hashedPassword);
 };

Here the 2 arguments will not be undefined, which is the cause of issue. And calling the function:

let hashCompare = this.compareHash(model.password, entity.password);
JoelSebstn
  • 522
  • 4
  • 5