Announcement

Collapse
No announcement yet.

JSON API HMAC issue

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    JSON API HMAC issue

    Heya, so I'm running into the 'Invalid Request Signature' error with the JSON API and I'm having a very difficult time getting anywhere.

    The API Token that I'm using is set up with both the HMAC signature and timestamp. The error seems to indicate the trouble is with the HMAC signature that I'm generating.

    I'm using a javascript backend to generate the POST request body and generate the HMAC (using CryptoJS).

    The HMAC generation looks a little like this:

    1. Base64 decode the private key string that's generated when the API token is created.
    2. Use the CryptoJS HmacSHA256 function with the JSON request body as the message and the base64 decoded private key as the key.
    3. Base64 encode the output of #2.
    4. Concatenate the different parts of the header: MIVA-HMAC-SHA256 <AccessToken>:<Base64EncodedHMAC>

    But this didn't work. Invalid Request Signature.

    So I set up a similar process in a PHP Sandbox, using a similar setup to the generateAuthHeader function shown on this page: https://docs.miva.com/json-api/

    And I get the same Base64 encoded HMAC as I did with the javascript implementation.

    Invalid Request Signature.

    Which leads me to think it might be one of the following:

    1) To create the JSON request body in the javascript implementation, I use JSON.stringify on an object that's structured in the way required for the API function that I'm using.

    For the PHP Sandbox test, I'm using the static string output from JSON.stringify of the POST request object in the javascript implementation.

    I'm wondering if there's some issue with using JSON.stringify to generate the POST request body? I'm not entirely sure how the Miva backend is authorizing the passed signature, but if it's doing things like parsing the JSON and re-stringifying it to check the HMAC validity, maybe there's some issues there (e.g., similar to the potential differences between PHP's json_encode and javascript's JSON.stringify()).

    2) The docs on https://docs.miva.com/json-api/, specifically the section with the generateAuthHeader function, are wrong. Gasp, how could I suggest a thing - I know, I know, but I only bring it up because Brennan's JSON API Webinar (here: https://vimeo.com/290769714 at the 16 minute mark) says to base64 ENCODE the private key before passing it to the HMAC function, even while the function to the right of the slide (and the docs above) say to decode the private key before passing it to the HMAC function.

    Just wondering if anyone here has ran into similar difficulties with the JSON API and can give me a push in the right direction. Thanks for any help!

    #2
    Can you post some sample code so I can help troubleshoot?

    A couple thoughts here. All the steps you provided sound correct. Likely the problem lies in the JSON payload you're passing into your HmacSHA256 function. This must match exactly the payload which is sent in the http request. If you're using JSON.stringify on your object prior to sending it in the http request, you'll also want to do this prior to passing it into your hmac function. If there is extra whitespace or misc line breaks when Miva goes to decode the JSON payload and compare it to the hmac passed it won't match.

    Thanks for finding the discrepancy in the docs and the webinar. The docs are correct, its the webinar where I had it mixed up. I'll get this corrected in the recording.

    Brennan Heyde
    VP Product
    Miva, Inc.
    [email protected]
    https://www.miva.com

    Comment


      #3
      Originally posted by Brennan View Post
      Can you post some sample code so I can help troubleshoot?

      A couple thoughts here. All the steps you provided sound correct. Likely the problem lies in the JSON payload you're passing into your HmacSHA256 function. This must match exactly the payload which is sent in the http request. If you're using JSON.stringify on your object prior to sending it in the http request, you'll also want to do this prior to passing it into your hmac function. If there is extra whitespace or misc line breaks when Miva goes to decode the JSON payload and compare it to the hmac passed it won't match.

      Thanks for finding the discrepancy in the docs and the webinar. The docs are correct, its the webinar where I had it mixed up. I'll get this corrected in the recording.
      Here's the javascript that I was using (this is from memory, but should be pretty close to what I actually wrote at work):

      Code:
      function getOrderData(storeId, orderId) {
          // storeId: string === Miva Store Code
          // orderId: string
          // storeInfoObj contains stuff like the access token, private key, etc.
          //      for a given store
      
          var storeInfo = storeInfoObj[storeId];
          var timeStamp = String(Math.floor(new Date() / 1000));
      
          var postBodyObj = {
              Store_Code: storeId,
              Function: "OrderList_Load_Query",
              Count: "1",
              Offset: 0,
              Filter: [
                  {
                      name: "search",
                      value: [
                          {
                              field: "id",
                              operator: "EQ",
                              value: orderId
                          }
                      ]
                  },
                  {
                      name: "ondemandcolumns",
                      value: [
                          "items"
                      ]
                  }
              ],
              Miva_Request_Timestamp: timeStamp
          };
      
          var postBodyJSON = JSON.stringify(postBodyObj);
      
          post({
              url: storeInfo.mivaAPIEndpoint
              body: postBodyJSON
              headers: {
                  'Content-type': 'application/json',
                  'X-Miva-API-Authorization': generateHMAC(storeInfo.mivaAPIAccessToken,
                      storeInfo.mivaAPIPrivateKey,
                      postBodyJSON)
              }
          });
      }
      
      function generateHMAC(accessToken, privateKey, bodyJSON) {
          var base64DecodedPrivateKey = CryptoJS.enc.Base64.parse(privateKey);
      
          var hmacHash = CryptoJS.HmacSHA256(bodyJSON, base64DecodedPrivateKey);
      
          var base64EncodedHmac = CryptoJS.enc.Base64.stringify(hmacHash);
      
          var digest = 'MIVA-HMAC-SHA256';
      
          return digest + ' ' + accessToken + ':' + hmacHash;
      }
      Thanks for the help! I did notice a slight difference in the Miva Github's Node JSON API SDK with how the HMAC is being generated (using something like hmac.update() & hmac.digest() instead of computing the HMAC in one go), but I'm not sure whether that would have a significant effect on the actual HMAC output.

      Last edited by new_user2018; 01-05-19, 09:56 PM.

      Comment


        #4
        Your code all looks correct. I found this which may be what is causing the discrepancy:

        https://stackoverflow.com/questions/...e64-encryption

        Miva is expecting the binary version not the hex version of the string.

        Can you give this a try?
        Brennan Heyde
        VP Product
        Miva, Inc.
        [email protected]
        https://www.miva.com

        Comment


          #5
          Originally posted by Brennan View Post
          Your code all looks correct. I found this which may be what is causing the discrepancy:

          https://stackoverflow.com/questions/...e64-encryption

          Miva is expecting the binary version not the hex version of the string.

          Can you give this a try?
          All right, I tried a few different things, all without working:

          1) The hmac being generated is already the binary version, but I used the alternate method described in the SO link you provided that looks a little like:

          Code:
          var hmacHash = CryptoJS.HmacSHA256(bodyJSON, base64DecodedPrivateKey).toString(CryptoJS.enc.Base64);
          2) I tried using the progressive hashing method (hmac.update & hmac.finalize) that looks like:

          Code:
          var hmacWordArray = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, base64DecodedPrivateKey).update(bodyJSON).finalize();
          
          var base64EncodedHmac = CryptoJS.enc.Base64.stringify(hmacWordArray);
          3) I tried changing the values for postBodyObj.Count & postBodyObj.Offset to either be both strings or both numbers.

          4) I tried adding function permissions for 'OrderList_Load_Query' to the API Key.

          When I tried each of these changes, I tested the docs' PHP implementation for the HMAC (with updated json body) and got the same result in both javascript and php implementations each time (and yes, the $raw_output arg for PHP's hash_hmac is set to true, so I am looking at the base64 encoded binary output).

          It almost seems like it's not the hmac generation that's the issue, but rather something funky perhaps with the authorization going on in json.mvc. I'm trying to look at the 9.12 LSK, but I think there's a few details that are not available to me (e.g., in the function JSON_API (/mmlsk_json.mv), where is l.calculated_signature generated, is anything done that changes the POST request body in s.content_data, etc.
          Last edited by new_user2018; 01-07-19, 11:05 AM.

          Comment


            #6
            I've also tried the POST request via Insomnia (in case the backend I'm using was doing something weird with the POST body) - I bumped up the timestamp window so that'd I have time to transfer the JSON body and auth header over, but I'm still getting an invalid request signature response.

            Comment


              #7
              Hmmm, ok, not sure if you changed anything on your end, but I just had a successful API request after generating a new private key. Not sure how that would have changed anything, as I had updated it several times before (and changed it in the script as well). Unless something weird happened where I didn't copy/paste it correctly or something dumb like that :/

              If it wasn't simple user error on my end (which, to be honest, probably isn't that far-fetched), I'm almost tempted to think there maybe some cache interaction at play with the generated private key? Maybe the post-mortem doesn't really matter all that much, now that it's working I can move on to the rest of the project :)

              Thanks for the help Brennan!

              Comment


                #8
                What was the final code you used that worked? I've been dealing with this same issue. I'm testing in Postman for the moment and can't figure out the hmac part. "Invalid request signature" error every time.

                Comment

                Working...
                X