When we are about to test a dynamic API request via Postman and realize that we need to pass a freshly generated signature in the headers, what do we generally do?
For each request, we generate the signature using server-side code and copy-paste it in the headers.
Sounds like a tedious job, doesn’t it?

We faced a similar situation in our application where we had to pass a signature in the headers while testing an API. The signature was generated by encoding all the request parameters using the user's secret key. If any of the request parameters changed, we had to regenerate the signature at the server-side console and copy-paste it in the headers.

This is where the Postman pre-request scripts came to our rescue. We no longer had to depend on the server-side console to generate the signature, we could make Postman itself generate it for us.

This article explains how to use the pre-request feature of Postman to generate a signature.

What are pre-request scripts?

As the name suggests and as mentioned in the Postman Documentation, these are small snippets of code in JavaScript that are executed before sending any request.
So, whenever you want to do some processing of parameters before sending them in any request, you can do it using the pre-request scripts.
There is a Pre-request Script tab beside the Body tab where we can add our scripts. These scripts are executed for us by the Postman Sandbox.
More information - here

Let us try to understand how pre-request scripts can help us in signing a request using the following practical example.

Simple Postman Request for an API without authentication

We will add an API endpoint in our application that returns a message in the response. Note that we have not added any kind of authentication yet. When we send the request via Postman, the API returns the message “Hello World”.

Postman Request without auth

We all know exposing the APIs without authentication/authorization could be risky. So we'll implement HMAC authentication for our APIs. Since our application is built on Ruby on Rails, we'll incorporate the APIAuth gem to enable authorization.

After the addition of the APIAuth code at the backend, it is now time to test the API using Postman.

Postman request for the same API with server-side authentication

Let us send the previous request again and see what we get.

Postman Request with auth

401 Unauthorized Error!!
This is predictable because the API is now expecting a signature to be sent in the request, from which it can decode and identify who made the request. Since we didn’t pass any signature, we were rendered unauthorized.

Building the pre-request script for signing the request

To prove the authenticity of our request we need to pass a signature in the headers.
For that, we'll refer to the APIAuth document and see what it says about signing the request. Then we will mock or replicate the same using pre-request scripts.
This is a snapshot of the APIAuth document explaining how the request has to be signed.

Api auth doc

Let’s go through each of these steps to generate the signature via Postman.

The first step tells us how to generate the canonical string

Api auth doc

Refer to the document provided by Postman postman-sandbox-api-reference, it provides several NodeJS libraries that can be used while building Postman scripts.

  • The HTTP method in our case is GET. Alternatively, we can use the pm:Object provided by the Postman Sandbox and get the HTTP method as pm.request.method. Further information available here
  • Since we do not need content-type and content-MD5 in the request we may keep them blank. Or we may fetch the content-type and content-MD5 using the getHeaders() method.
pm.request.getHeaders()['Content-Type'] || '';
pm.request.getHeaders()['Content-MD5'] || '';
  • The request URI is the API path without the domain. In our case, it is /api/v1/messages. We can also get this by using the function getPathWithQuery() provided by the Sandbox as:
pm.request.url.getPathWithQuery();
  • Now lastly we need the timestamp.  We can use the moment library to generate the timestamp. We'll call the moment() function, and store the current time in a temporary variable timestamp.
const moment = require('moment');
let timestamp = moment().utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT';

Note: We have converted the timezone to UTC as our application compares the time in UTC format

Now that we have all the values, we’ll build the canonical string as follows

let canonical_string = pm.request.method + "," + 
  (pm.request.getHeaders()['Content-Type'] || '') +"," +
  (pm.request.getHeaders()['Content-MD5'] || '') + "," +
  pm.request.url.getPathWithQuery() + "," + timestamp;

or simply in our case, it is

let canonical_string = “GET,,,/api/v1/messages,” + timestamp

The next step says we have to encode the above canonical string using the client's private secret key.

Api auth doc

This secret_key would be a random string associated with each user (stored in Database
or any other storage engines). Let us fetch that value for the user and store it in a
variable.

const secret_key = “SECRET_KEY”

Now we need to encode it using HMAC SHA1 algorithm. For this, we will use the
CryptoJS library provided by the Postman Sandbox - here

Note: We can click the links in this site to view the documentation for each library.

As per the crypto-js document, we can generate the encoded message as follows:

const hmac_encoded_str = CryptoJS.HmacSHA1(canonical_string, secret_key)

As mentioned in the above step the signature must be a Base64 encoded HMAC SHA1 string. So we need to further encode it using Base64.

const signature = hmac_encoded_str.toString(CryptoJS.enc.Base64)

That's it!! We have successfully generated the signature.

The third step says that we have to add the signature in the headers of the request.

Api auth doc

But we have the signature as a local variable. How do we make it accessible in the headers?
The solution is to use an environment variable or a global variable.
For simplicity, we will set the variables as global. To do that we can use the pm object provided by the Sandbox environment. You could also use postman.setGlobalVariable(‘signature’, signature) but that has been deprecated in the newer versions. You can learn more about it - here

pm.globals.set(‘signature’, signature);

This is how the combined script looks in Postman.

Api auth doc
Api auth doc

As you can see we have added the Authorization header with the value in the format APIAuth 1:{{signature}}

Note:
Client access Id is the unique id of the user whose secret_key we had used, equal to 1 in our case.
As soon as we enter {{ in the value field, it lists all the global/environment variables available. We just have to select signature from the list.

We'll send the request again with the DATE and Authorization included in the headers.

Api auth doc

You can see we get the message Hello World in the response. This means we have successfully generated the signature using a pre-request script.

Similarly, if we know the procedure for signing any API request, we can use the NodeJS libraries provided by Postman to generate that signature.

Hope this article has helped you in knowing how to use the pre-request feature of Postman to test dynamic APIs. Please do try creating such scripts for other APIs and let us know if it helped.

Thank you for reading.

References

https://learning.postman.com/docs/postman/scripts/postman-sandbox-api-reference/

https://www.npmjs.com/package/crypto-js

https://cryptojs.gitbook.io/docs/