How to use HMAC with Postman to test webhooks

Norwegian Finish Border near Neiden Näätämö Finland
Norwegian Finish Border near Neiden Näätämö Finland

In this how-to, I will show how I implemented HMAC in a Cloud Function that acts as a webhook using the node Express application framework. The focus of this article is how I implemented HMAC and not on how to build a Cloud Function webhook.

A webhook is an HTTPS callback, that sends events to the URL endpoint specified, events can be used to power integrations with other systems.

What is HMAC

A message authentication code (MAC) is produced from a message and a secret key by a MAC algorithm. An HMAC is a MAC which is based on a hash function. The basic idea is to concatenate the key and the message and hash them together.

A MAC authenticates a message, in simple terms, the client computes a signature of the message, and include the signature in the request. The receiving application when receiving the client request, will make its own computation and create a signature of the message received, If the message received is the same as the signature in the request the message has not been tampered with.

An HMAC by itself does not provide message integrity. It can be one of the components in a protocol that provides integrity.

Postman setup

So to test an HMAC implementation – I been using Postman to generate the POST or GET request to test my Cloud Function. To setup Postman Post request I did the following to get a HMAC signature

Header

In the header of a request, I had to add a key with a variable that picked up the Pre-request Script execution to get the signature, as shown in this message.

Body

I also created a json structure data in the body of the request. This is just an example, the signature is computed against in this case the json data in the body, as show in this image.

Pre Request Script

The pre-request script is executed before the request is sent from Postman, it is a simple script that will create a signature of the request body and place that in the header, as shown in this image.

And below is the code snippet you can copy to use

postman.setEnvironmentVariable("hmac", CryptoJS.HmacSHA256(request.data, 'your secret').toString(CryptoJS.digest));

Cloud Function

In the use case I was working on the Express app was implemented as a Cloud Function running on GCP.

There was a few things I had to do

Crypto

For the cryptography functionality, I used the Node Crypto module. The crypto module is a wrapper for OpenSSL cryptographic functions. It supports calculating hashes, authentication with HMAC, ciphers, and more!

You would want to add a check if the is crypto module is installed, I did that the following way. Further down you will see how I incorporate that into my code.

// Checking if Crypto installed and require crypto
let crypto;
try {
  crypto = require('crypto');
} catch (err) {
  console.log('crypto support is disabled!');
}

Google Secret Manager

For the secret that I use in the Postman Pre-request script, on the Cloud Function side I setup the secret using the Google Cloud Secret manager. If you want to learn how to use the Google Secret Manager you can read this article Using Secret Manager, the example in the Article is for Python. I will show I did it with javascript.

Load the libraries

// Import the Secret Manager client and instantiate it:
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
const secretClient = new SecretManagerServiceClient();

To get the secret from the Google Cloud Secret manager, I created a function that does all the work of accessing the secret manager API and gets the secret. Do make this work for you do not forget to enable the Secret Manager API on the Google Cloud Platform. Here is the function code:

/**
 * Get secret from Google Secret manager.
 * @param {text} secretkey The name of the secret.
 * @return {text} The secret value.
 */
async function getSecret(secretkey) {
  // Access the secret.
  const resourceName =
    'projects/' +
    process.env.PROJECT +
    '/secrets/' +
    secretkey +
    '/versions/latest';
  try {
    const [version] = await secretClient.accessSecretVersion({
      name: resourceName,
    });
    return version.payload.data.toString();
  } catch (err) {
    logger.error(err.toString());
  }
}

To access the getSecret function from other parts of my code, I just make a function call to getSecret like this.

  // Get Secret
  const SHARED_SECRET = await getSecret(
    'csl_incoming_webhook_dev_test_signing_secret'
  );

Verifying the HMAC signature

Now we come to the part where the work happens of verifying the HMAC signature, I first created a function for the actual HMAC local creation and verification. The code is straight forward, I passing to variables to the function the request body and the secret, that I got from the Google Cloud Secret manager.

/**
 * Sign the Add two numbers.
 * @param {string} stringSign The string to be hashsed.
 * @param {sharedsecret} sharedSecret The secret to hash with.
 * @return {number} The hash.
 */
async function verifySignature(stringSign, sharedSecret) {

   // Checking if Crypto installed and require crypto
   let crypto;
   try {
      crypto = require('crypto');
      return crypto
        .createHmac('SHA256', sharedSecret)
        .update(stringSign)
        .digest('hex');
   } catch (err) {
      console.log('crypto support is disabled!');
      return false
   }
}

To call the verifySignature, in my code I had an if statement, to check if the new signature matches the signature sent from the client and set in request header like this req.headers.hmac

if (
    (await verifySignature(Buffer.from(req.rawBody, 'utf8'), SHARED_SECRET)) ===
    req.headers.hmac
  ) {

The request body

To be able to get the request body, I had to setup Express use to get the raw request.body from the request, I could not use:

JSON.stringify(req.body)

As it just gives you the json string, which should be enough it was not. I had to find another way, and as I said the Cloud Functions uses Express. This was done instead to get the raw request body.

app.use(bodyParser.json());

// This is for the HMAC validation
app.use(
  bodyParser.json({
    verify: function (req, res, buf) {
      req.rawBody = buf;
    },
  })
);

And I created an app routing path in Express, when the request from the Postman client reach my Cloud Function, I take a few steps as show in the code:

app.post('/hmactest', async function (req, res, next) {
  // Get Secret
  const SHARED_SECRET = await getSecret(
    'csl_incoming_webhook_dev_test_signing_secret'
  );

  // Check computed Signature with Signature from request body
  if (
    (await verifySignature(Buffer.from(req.rawBody, 'utf8'), SHARED_SECRET)) ===
    req.headers.hmac
  ) {
    console.log('Valid signature');
    return res.status(200).send({status: 'OK'});
  } else {
    console.log('Invalid signature');
    return res.status(500).send({status: 'Hash Error'});
  }
});

Conclusion

It does not look like a lot of code and that is through, the code packs a punch and makes a simple action to validate an HMAC signature – this was just a simple way to implement HMAC signature working with a Postman as a client.

If you are using a real system that sending you messages, all you would need to do is to make sure we’re the signature is sent from the client so you can pick it up, in my case I created a key in the request header were I picked up the client signature from, which I accessed with req.headers.hmac

Leave a reply:

Your email address will not be published.