Using JavaScript to properly sign a string using HmacSHA256

BannerMan picture BannerMan · Feb 5, 2016 · Viewed 20.6k times · Source

In the Houndify API Docs for Authentication, you have the following block of content:


An Example of Authenticating a Request

Let's assume we have the following information:

UserID: ae06fcd3-6447-4356-afaa-813aa4f2ba41
    RequestID: 70aa7c25-c74f-48be-8ca8-cbf73627c05f
    Timestamp: 1418068667   
    ClientID: KFvH6Rpy3tUimL-pCUFpPg==
    ClientKey: KgMLuq-k1oCUv5bzTlKAJf_mGo0T07jTogbi6apcqLa114CCPH3rlK4c0RktY30xLEQ49MZ-C2bMyFOVQO4PyA==
  1. Concatenate the UserID string, RequestID string, and TimeStamp string in the following format: {user_id};{request_id}{timestamp}

  2. With the values from the example, the expected output would be in this case: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f1418068667

  3. Sign the message with the decoded ClientKey. The result is a 32-byte binary string (which we can’t represent visually). After base-64 encoding, however, the signature is: myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=

  4. The client then generates two authentication headers Hound-Request-Authentication and Hound-Client-Authentication.

  5. The Hound-Request-Authentication header is composed by concatenating the UserID and RequestID in the following format: {user-id};{request-id}. Continuing the example above, the value for this header would be: Hound-Request-Authentication: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f

  6. The Hound-Client-Authentication header is composed by concatening the ClientID, the TimeStamp string and the signature in the following format: {client-id};{timestamp};{signature}. Continuing the example above, the value for this header would be: Hound-Client-Authentication: KFvH6Rpy3tUimL-pCUFpPg==;1418068667;myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=


For Number 3, it says "Sign the message with the decoded ClientKey". The "message" and "ClientKey" are two distinct strings.

My question(s): How do you sign one string with another string i.e. what exactly does that mean? And how would you do that in JavaScript?

var message = 'my_message';
var key = 'signing_key';

//??what next??

I'm trying to figure all this out so I can create a pre-request script in Postman to do a proper HmacSHA256 hash.

Answer

Sam picture Sam · Feb 5, 2016

According to the documentation, if you're using one of their SDKs, it will automatically authenticate your requests:

SDKs already handle authentication for you. You just have to provide the SDK with the Client ID and Client Key that was generated for your client when it was created. If you are not using an SDK, use the code example to the right to generate your own HTTP headers to authenticate your request.

However, if you want to do it manually, I believe you need to compute the HMAC value of the string they describe in the link in your question and then send it base64 encoded as part of the Hound-Client-Authentication header in your requests. They provide an example for node.js:

var uuid = require('node-uuid');
var crypto = require('crypto');

function generateAuthHeaders (clientId, clientKey, userId, requestId) {

    if (!clientId || !clientKey) {
        throw new Error('Must provide a Client ID and a Client Key');
    }

    // Generate a unique UserId and RequestId.
    userId      = userId || uuid.v1();

    // keep track of this requestId, you will need it for the RequestInfo Object
    requestId   = requestId || uuid.v1();

    var requestData = userId + ';' + requestId;

    // keep track of this timestamp, you will need it for the RequestInfo Object
    var timestamp   = Math.floor(Date.now() / 1000),  

        unescapeBase64Url = function (key) {
            return key.replace(/-/g, '+').replace(/_/g, '/');
        },

        escapeBase64Url = function (key) {
            return key.replace(/\+/g, '-').replace(/\//g, '_');
        },

        signKey = function (clientKey, message) {
            var key = new Buffer(unescapeBase64Url(clientKey), 'base64');
            var hash = crypto.createHmac('sha256', key).update(message).digest('base64');
            return escapeBase64Url(hash);

        },

        encodedData = signKey(clientKey, requestData + timestamp),
        headers = {
            'Hound-Request-Authentication': requestData,
            'Hound-Client-Authentication': clientId + ';' + timestamp + ';' + encodedData
        };

    return headers;
};