Sulphy
Sulphy

Reputation: 776

HMAC authentication via Postman

I'm using an example for setting up HMAC authentication for a Web API project. The original example source code/project is available here:

http://bitoftech.net/2014/12/15/secure-asp-net-web-api-using-api-key-authentication-hmac-authentication/

I'm trying to get Postman to construct and send a GET request in it's pre-request script. However the request always fails with a 401 and I can't figure out why.
Postman pre-request script:

var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = "http%3a%2f%2flocalhost%3a55441%2fapi%2fv1%2fdata";
var requestMethod = "GET";
var requestTimeStamp = "{{$timestamp}}";
var nonce = "1";
var requestContentBase64String = "";

var signatureRawData  = AppId + requestMethod + requestURI + requestTimeStamp +  nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray)

var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
postman.setGlobalVariable("key", "amx " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp);

Upvotes: 13

Views: 33515

Answers (3)

CircleUpX
CircleUpX

Reputation: 11

If you are having trouble with the script provided by Florian SANTI and you are using Postman v8.0 or higher. You'll need to make sure that the empty string is set by the variable requestContentBase64String is set properly by correctly inspecting the request body.

This is how I solved the issue.

var uuid = require('uuid');
var moment = require("moment")
var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = encodeURIComponent(pm.environment.values.substitute(pm.request.url, null, false).toString()).toLowerCase();
var requestMethod = pm.request.method;
var requestTimeStamp = moment(new Date().toUTCString()).valueOf() / 1000;
var nonce = uuid.v4();

var hasBody = (pm.request.body !== null);
if (hasBody) {
    hasBody = (!pm.request.body.isEmpty);
}
var requestContentBase64String = "";
if (hasBody) {
    var md5 = CryptoJS.MD5(JSON.stringify(postBody));
    requestContentBase64String = CryptoJS.enc.Base64.stringify(md5);
}

var signatureRawData  = AppId + requestMethod + requestURI + requestTimeStamp +  nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray);
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);
var hmacKey =  "amx " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp;
postman.setEnvironmentVariable("hmacKey", hmacKey );

Upvotes: 1

Florian SANTI
Florian SANTI

Reputation: 551

This is the code I'm using in my Pre-Script. It works for any query GET, PUT, POST, DELETE.

You need to change the AppId & the APIKey values and on the last line adjust the name of the environment variable "hmacKey" with yours.

function interpolate (value) {
    const {Property} = require('postman-collection');
    return Property.replaceSubstitutions(value, pm.variables.toObject());
}

var uuid = require('uuid');
var moment = require("moment")

var hmacPrefix = "hmac";
var AppId = "4d53bce03ec34c0a911182d4c228ee6c";
var APIKey = "A93reRTUJHsCuQSHR+L3GxqOJyDmQpCgps102ciuabc=";
var requestURI = encodeURIComponent(pm.environment.values.substitute(pm.request.url, null, false).toString().toLowerCase());
var requestMethod = pm.request.method;
var requestTimeStamp = moment(new Date().toUTCString()).valueOf() / 1000;
var nonce = uuid.v4();
var requestContentBase64String = "";
var bodyString = interpolate(pm.request.body.toString());

if (bodyString) {
    var md5 = CryptoJS.MD5(bodyString);
    requestContentBase64String = CryptoJS.enc.Base64.stringify(md5);
}

var signatureRawData  = AppId + requestMethod + requestURI + requestTimeStamp +  nonce + requestContentBase64String; //check
var signature = CryptoJS.enc.Utf8.parse(signatureRawData);
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var signatureBytes = CryptoJS.HmacSHA256(signature,secretByteArray);
var requestSignatureBase64String = CryptoJS.enc.Base64.stringify(signatureBytes);

var hmacKey = hmacPrefix + " " + AppId + ":" + requestSignatureBase64String + ":" + nonce + ":" + requestTimeStamp;
postman.setEnvironmentVariable("hmacKey", hmacKey);

Upvotes: 17

Sulphy
Sulphy

Reputation: 776

After a few days of testing I figured out the problem. It was actually to do with the variable placeholders provided by Postman of all things. In testing the placeholder {{$timestamp}} at face value was passing a valid value. When I stripped the signature back to start with just a single segment I was getting authenticated successfully. Until of course I put the timestamp placeholder back in.

When I swapped out the placeholder for the actual value passed in the header it worked fine. I can only conclude that there must be some extra character I can't see. Perhaps on the Postman side when it creates the signature. The problem extends to other placeholders such as {{$guid}}.

Upvotes: 2

Related Questions