HMAC Authentication with dynamic tokens

Hi Everyone,

Hoping you might be able to help with guiding me on how to get dynamic tokens that are generated using HMAC. The API I’m busy with has been set up for testing through Postman, but I’d like to automate using RFW. Authentication consists of a Key, Secret and Auth token. The key and secret always remain the same and are set up using environment variables. There is a prerequest script at collection level that runs to generate a new token each time a call is executed. That token is then passed into an Authorization variable in the header of each request. The key, secret and token aren’t passed to the URL, so setting params or data breaks the call execution and results in 500s.
Does anyone have any suggestions please?
The code below is an adaptation of what I’ve used previously for standard JWT authentication, but when I execute now, it’s throwing a 401:

create session  mysession  ${BASE_URL}
${data1}=  create dictionary  Key=${KEY}  Secret=${SECRET}   
${headers}=  create dictionary  Content-Type=application/json
${resp}=  GET On Session  mysession  /api/v2/Batch  headers=${headers}  data=${data1}
${accessToken}=    evaluate    $resp.json().get("token")
Log to Console  ${accessToken}
${Bearer}=  set variable  LL
${token}=  catenate  LL  ${accessToken}
Log to Console     ${token}
Set Suite Variable  ${token}

Hi Jasmine,

I’ll try to help, I’ll assume for now this part of your script is working and you are getting an access token?

create session  mysession  ${BASE_URL}
${data1}=  create dictionary  Key=${KEY}  Secret=${SECRET}   
${headers}=  create dictionary  Content-Type=application/json
${resp}=  GET On Session  mysession  /api/v2/Batch  headers=${headers}  data=${data1}
${accessToken}=    evaluate    $resp.json().get("token")
Log to Console  ${accessToken}

If not perhaps what you need to do is (I’m guessing here):

${headers}=  create dictionary  Content-Type=application/json  Key=${KEY}  Secret=${SECRET}

After you get your access token, based on your description I think you then need to start with Set To Dictionary to add to the headers variable then use Update Session to update the session, something like this:

Set To Dictionary	${headers}	LL=${accessToken}
Update Session   mysession   headers=${headers}

As for the rest of the script, i’m not sure what your doing there, I think your prepending LL to the accessToken? so that might need to go before the Set To Dictionary, and you then might need to use ${token} instead of ${accessToken} in Set To Dictionary

${Bearer}=  set variable  LL
${token}=  catenate  LL  ${accessToken}
Log to Console     ${token}
Set Suite Variable  ${token}

Not sure how much help this was, but if you can show me the working raw request & responses from postman I might be able to help better, as I’m just guessing at the moment.

Dave.

Hi Dave,

Thank you so much for your prompt feedback! I tried out what you suggested and I’m still getting a 401 returned. There is also no token that is logged to console. I asked our architect if the key, secret & token are passed to the URL and he said no, “It’s (key & secret) used to encode the authentication token on the client side” “token isn’t sent in the url either. It’s encoded as part of the request header”. This is the pre-request script that runs at collection level to generate the token and pass it to the header each time a call is executed:

var h = CryptoJS, j = h.lib.WordArray; h.enc.Base64 = {
stringify: function (b) { var e = b.words, f = b.sigBytes, c = this._map; b.clamp(); b = []; for (var a = 0; a < f; a += 3) for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) b.push(c.charAt(d >>> 6 * (3 - g) & 63)); if (e = c.charAt(64)) for (; b.length % 4;) b.push(e); return b.join("") }, parse: function (b) {
    var e = b.length, f = this._map, c = f.charAt(64); c && (c = b.indexOf(c), -1 != c && (e = c)); for (var c = [], a = 0, d = 0; d <
    e; d++) if (d % 4) { var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4), h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4); c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4); a++ } return j.create(c, a)
}, _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
}
var getTicks = function () {
return Math.floor(Date.now() / 1000);
}
var getNonce = function () {
function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4();
};
function getHMAC (key, method, url, timestamp, nonce, contentBase64, secret) {
var hash = CryptoJS.HmacSHA256(key + method + url + timestamp + nonce + contentBase64, CryptoJS.enc.Base64.parse(secret));
return CryptoJS.enc.Base64.stringify(hash);
};
function signRequest (method, url, body, key, secret, timestamp) {
var contentBase64 = '';

if (body) {                                
    var encryptedBody = CryptoJS.MD5(body);
    contentBase64 = encryptedBody.toString(CryptoJS.enc.Base64);                
}
var nonce = getNonce();
var signature = getHMAC(key, method, encodeURIComponent(url).toLowerCase(), timestamp, nonce, contentBase64, secret);
var output = 'LL ' + key + ':' + signature + ':' + nonce + ':' + timestamp;
return output;
};
function createAuthHeader(method, url, body, key, secret)
{
console.info(method);
var timestamp = getTicks();
var hash = signRequest(method, url, body, key, secret, timestamp);
return hash;            
};

var url = pm.environment.get('BaseUrl') + pm.request.url.getPathWithQuery();

var method = pm.request.method;

var key = pm.environment.get('Key');
var secret = pm.environment.get('Secret');
var body = pm.request.body.raw;
var header = createAuthHeader(method,url,body, key, secret);

//pm.request.headers.add({key: 'Authorization', value: header })
pm.environment.set('Authorization', header);

Header variable:

Token generated as a result of HMAC script is placed in an environment variable to be passed to the header. This updates each time the call is executed ie. token is dynamic:

After call execution, this is what is returned in the response header:

There are also cookies:

I’m thinking now that perhaps I don’t need to set a token to pass to other tests. I just need to get a new token for each individual test because that’s what happens on call execution. Here is the report after running with the suggested changes:

I hope this provides more clarity. If you need any more info, please let me know :slight_smile:

Hi Jasmine,

So I understand your problem, you need to use the key and secret to generate the token before you send the first request, fortunately they’ve given you a code example of how to generate the token but it’s looks like it’s in in javascript? or ecmascript?

I am not at an advanced level in these languages, but can read them to have some understanding of what is happening. there are 2 parts to that script, the functions at the top and the variables at the bottom that get values from postman and then generate the auth header token and push that back to postman.

As robot framework doesn’t run javascript or ecmascript directly, I can think of 2 options for you, both have pro’s and con’s:

  1. Rewrite these functions as python functions so that robot framework can call the python function and generate the token for you.

  2. Use Js2Py to call the javascript / ecmascript from python
    (some more example of usage here Run JavaScript In Python With Code For Beginners | Program Solve )

Personally i’m leaning towards the second option, that way you are using their code to generate the token but it’s likely to be slower in the test execution, hopefully not a lot slower, it also means you have another dependancy. but on the upside if they change the token generation formula you can just drop in the new version of their code.

I would suggest creating a python library for robot framework (creating test library class or module) probably called HMAC (class & python file name) with a single function inside called something like create_auth_header(method, url, body, key, secret) this python function would use Js2Py to call the javascript/ecmascript function createAuthHeader passing the values straight through, and return the result.
Then in your robot framework test suite you just import the HMAC library and call the python function as a keyword something like this:

${TOKEN}=    Create Auth Header    GET    ${BASE_URL}/api/v2/Batch    ${EMPTY}    ${KEY}    ${SECRET}

Note: python function name of create_auth_header translates as robot framework keyword Create Auth Header that wasn’t clear to me when I created my first python library.

Then it’s just a simple matter of adding the token into the headers.

Not sure your programming skills, you may need help if there, this is not the easiest thing to implement, but done the way I suggested you’ll have a reusable keyword that you can use for many many test cases. If you’re not strong on python coding try finding some one in your org who is to help you, but if you manage to get this all working yourself it’ll be a great learning experience.

Hope this helps,

Dave.