Michael Atterbury
Michael Atterbury

Reputation: 83

NodeJS equivalent of C# code for hmac-sha256 authorization

Im trying to convert the C# code found here: AMX Authorization Header in order to connect to an external API. The C# code works when trying to connect to the external API but when I convert it to a nodeJS solution it doesnt work.

I dont have access to the external C# API so can't update that side but was hoping someone could look at this and see something Im missing or doing wrong:

My nodejs solution:

var request = require('request');
var uuid = require('node-uuid');
var CryptoJS = require('crypto-js');

var URL = "https://urltoexternalAPI.com";
var itemAPPId = "testAPPId";
var APIKey = "testAPIKey";

var requestUri = encodeURIComponent(URL.toLowerCase());
var requestHttpMethod = "GET";

var requestTimeStamp = Math.floor(new Date().getTime() / 1000).toString();

var nonce = uuid.v1().replace(/-/g, '');

//I excluded the content hashing part as the API Im hitting is a GET request with no body content
var signatureRawData = itemAPPId + requestHttpMethod + requestUri + requestTimeStamp + nonce;

var secretKeyByteArray = CryptoJS.enc.Base64.parse(APIKey);

var signature = CryptoJS.enc.Utf8.parse(signatureRawData);

var signatureBytes = CryptoJS.HmacSHA256(signature, secretKeyByteArray);

var requestSignatureBase64String = signatureBytes.toString(CryptoJS.enc.Base64);

request({
  url: URL,
  headers: {
    'Authorization': "amx "+itemAPPId+":"+requestSignatureBase64String+":"+nonce+":"+requestTimeStamp
  }
}, function (error, response, body) {
  if (response.statusCode != 200) {
    console.log("Fail");
  } else {
    console.log("Success");
  }
});

Upvotes: 0

Views: 1027

Answers (1)

Michael Atterbury
Michael Atterbury

Reputation: 83

I figured it out! If anyone ever comes across this issue they may find the below helpful:

the following C# code works a little different to nodeJS: System.Web.HttpUtility.UrlEncode(request.RequestUri.AbsoluteUri.ToLower());

Initially I copied this functionality as is and wrote the nodejs equivalent as such:

 var requestUri = encodeURIComponent(URL.toLowerCase());

The encoding of the URL in C# keeps everything in lowercase - for e.g: https:// becomes https%3a%2f%2f - whereas nodeJS uppercases its encoding characters - https%3A%2F%2F - this is what as causing the incorrect hashing.

The solution is to just move the lowercase function to after the encoding has been done on the URL. Like so:

var requestUri = encodeURIComponent(URL).toLowerCase();

Seems rather simple but when trying to replicate the C# solution you may not pick up that the two URL encoders work differently.

Final solution: (updated to crypto thanks to Yoryo)

const fetch = require("node-fetch");
const uuid = require("uuid");
const crypto = require('crypto');

var URL = "https://urltoapi.com";

var itemAPPId = config.itemAPPId;
var APIKey = config.itemAPIKey;

var requestUri = encodeURIComponent(URL).toLowerCase();
var requestHttpMethod = "GET"; //should be dynamic

var requestTimeStamp = Math.floor(new Date().getTime() / 1000).toString();

var nonce = uuid.v1().replace(/-/g, '');
var signatureRawData = itemAPPId + requestHttpMethod + requestUri + requestTimeStamp + nonce;

var key = Buffer.from(APIKey, 'base64');
var requestSignatureBase64String = crypto.createHmac('sha256', key).update(signatureRawData, 'utf8').digest('base64');

const hitExternalAPI = async url => {
  try {
    const res = await fetch(url, { method: 'GET', headers: { "Authorization": "amx "+itemAPPId+":"+requestSignatureBase64String+":"+nonce+":"+requestTimeStamp } })
    .then(res => {
      console.log(res.ok);
    });
  } catch (error) {
    console.log("Error",error);
  }
};
hitExternalAPI(URL);

Upvotes: 1

Related Questions