Last updated: Aug 05, 2022

Create a Hashing function using built in Crypto module of Node.js

Create a hashing function to hash a secret string provided by our API consumer in JSON payload. We use HMAC that stands for Hash Based Message Authentication which is a cryptographic authentication technique.

It is only for hashing a string or an object so that we can later validate the authenticity of future request made by our user. It can not decrypt the message. To encrypt or decrypt use Cipher.

//...

app.post('/hash', (req, res) => {
    const { body } = req;
    if (typeof body !== 'object') {
      //
      // ---> Since we are using json an object is required to create a hash

      res.status(400).json({
        message: \`An object with secret key/value pairs is needed to generate a hash\`,
      });
    } else {
      //
      const hash = createHmac('sha256', process.env.SECRET)
        .update(JSON.stringify(body.password))
        .digest('base64');

      //  ---> our api consumers share a password and we store it in memory with a hash
      try {
        fs.writeFileSync(
          path.resolve(__dirname, \`./data.json\`),
          JSON.stringify({ hash })
        );
      } catch (err) {
        console.log(err);
      }

      res.status(201).json({
        message: 'Include your password as "api-key" in request headers',
      });
    }
  });

The purpose of this verification is to find out if the request is coming from a valid api consumer that we trust. We stored the password as hash so even if the hash value gets compromised, the attacker won't be able to guess the password.

//...
app.get('/protected', hmacVerify(fs, path), (req, res) => {
    res.json({ message: 'Your are now visting a protected route' });
  });

In the code block below, we created a middleware that reads the api-key set in the request headers and feeds it to the algorithm for comparison.

const { createHmac, timingSafeEqual } = require('node:crypto');

const hmacVerify = (fs, path) => {
  return (req, res, next) => {
    try {
      //
      // read the hash that we stored earlier in a json file
      const file = fs.readFileSync(
        path.resolve(__dirname, '../data.json'),
        'utf-8'
      );

      // ----> here we generate a hash using our secret key
      //       by reading the password of user in the headers

      const generatedHash = createHmac('sha256', process.env.SECRET)
        .update(JSON.stringify(req.headers['api-key']), 'utf-8')
        .digest('base64');

      if (
        // ----> compares without leaking timing info
        //  -    could allow an attacker to guess one of
        //  -    the values otherwise

        timingSafeEqual(
          Buffer.from(generatedHash),
          Buffer.from(JSON.parse(file).hash)
        )
        //
      ) {
        return next();
      } else {
        res.status(401).json({ error: 'Failed to verify the hmac header' });
      }
    } catch (e) {
      console.log(e);
      return res.status(400).json({ error: 'Something broke this route' });
    }
  };
};

module.exports = hmacVerify;

An important thing to note here is that we use timingSafeEqual method from crypto module to campare the values without leaking the timing info.

We have created a middleware that uses the built-in Node.js module to verify the request headers by hashing the secret or key passed in the request headers by our API consumer.

A Github repository for the complete code and setup is available at this repo-link.