Node.js REST API "invalid signature: oauth_signature" (but I think I'm close!)

3,145 views
Skip to first unread message

James Splaine

unread,
Nov 11, 2013, 5:06:25 PM11/11/13
to fatsecret-p...@googlegroups.com
Hi everyone!

I'm using the info here to use the REST API : http://platform.fatsecret.com/api/Default.aspx?screen=rapiauth

3 questions: 

1) "Consumer Secret" as mentioned in link above == "REST API Shared Secret", as seen in my API KEY Details page logged in on fatsecret.com?

2) If I try to get this working locally, and not on the website I've registered for this key (www.hivemindhealth.com), it will not work?  (Will I see "invalid oauth_signature" because of that?).  I get the same error when doing from that site incidentally.

3) Can you look at my code (See below) and tell me what's going on?  I've spent a good, oh .. 4 hours trying to figure it out :(.   I've hid my api and sharedSecret keys.

 here's an example signature base string my code creates:

POST&http%3A%2F%2Fplatform.fatsecret.com%2Frest%2Fserver.api%26method%3Dfoods.search%26oauth_consumer_key%3D549396a7628644e48ea0351c35858883%26oauth_nonce%3D1jzw15h3fas8aor%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1384206855%26oauth_version%3D1.0%26search_expression%3Dbanana

 .. and an example oauth sig:

 vmzuncdpSg3Jk7Gw9wwtyKYlbhE%3D

Thanks in advance!

Jay

node.js code:

// Dependencies and constants
var rest             = require('restler'),
    crypto           = require('crypto'),
    apiKey           = 'xxx',
    sharedSecret     = 'xxx',
    date             = new Date;

// Note that the keys are in alphabetical order
var reqObj = {
  method: "foods.search",
  oauth_consumer_key: apiKey,
  oauth_nonce: Math.random().toString(36).replace(/[^a-z]/, '').substr(2),
  oauth_signature_method: 'HMAC-SHA1',
  oauth_timestamp: Math.floor(date.getTime() / 1000),
  oauth_version: "1.0",
  search_expression: "banana" // test query
};

// construct a param=value& string and uriEncode
var paramsStr = '';
for (var i in reqObj) {
  paramsStr += "&" + i + "=" + reqObj[i];
}
var sigBaseStr = "POST&"
                 + encodeURIComponent(fatSecretRestUrl)
                 + encodeURIComponent(paramsStr);
console.log("sig base str = " + sigBaseStr);

// HMAC SHA1 has
var hashedBaseStr = encodeURIComponent(crypto.createHmac('sha1', sharedSecret + '&').update(sigBaseStr).digest('base64'));
console.log("oauth_sig = " + hashedBaseStr);

// Add oauth_signature to the request object
reqObj.oauth_signature = hashedBaseStr;

// Launch!
rest.post(fatSecretRestUrl, {
  data: reqObj,
}).on('complete', function(data, response) {
  console.log(response);
  console.log("DATA: " + data);
});

James Splaine

unread,
Nov 12, 2013, 5:40:24 PM11/12/13
to fatsecret-p...@googlegroups.com
K, found one issue where I was putting a "&" at the beginning of my paramsStr, which is incorrect.

Fixed that -- also verified that the method/library I was using for creating the hmacSHA1 hash is sane (by comparing the hashes it creates with the hashes another hmacSHA1 implementation creates).

Still .. hour oh, 5 or so of me slamming my head against this and I'm still getting the oauth_sig error.

Could someone in-the-know take a look at my script?    

James Splaine

unread,
Nov 12, 2013, 7:46:55 PM11/12/13
to fatsecret-p...@googlegroups.com
EPIC UPDATE!

Ok, it works.  I was URI encoding my oauth_sig where I shouldn't have been.

So here's the working code for anyone to reference.  This is functioning node.js calling foods.search w/no authenticated user.  FatSecret returns XML in this case.  The API key and REST secret / shared secret have been replaced with  'xxx'.

Enjoy!

// Dependencies and constants
var rest              = require('restler'),
    crypto            = require('crypto'),
    apiKey           = 'xxx',
    sharedSecret     = 'xxx',
    date             = new Date;

// Note that the keys are in alphabetical order
var reqObj = {
  method: 'foods.search',
  oauth_consumer_key: apiKey,
  oauth_nonce: Math.random().toString(36).replace(/[^a-z]/, '').substr(2),
  oauth_signature_method: 'HMAC-SHA1',
  oauth_timestamp: Math.floor(date.getTime() / 1000),
  oauth_version: '1.0',
  search_expression: 'banana' // test query
};

// construct a param=value& string and uriEncode
var paramsStr = '';
for (var i in reqObj) {
  paramsStr += "&" + i + "=" + reqObj[i];
}

// yank off that first "&"
paramsStr = paramsStr.substr(1);

var sigBaseStr = "POST&"
                 + encodeURIComponent(fatSecretRestUrl)
                 + "&"
                 + encodeURIComponent(paramsStr);

// no  Access Token token (there's no user .. we're just calling foods.search)
sharedSecret += "&";

var hashedBaseStr  = crypto.createHmac('sha1', sharedSecret).update(sigBaseStr).digest('base64');

// Add oauth_signature to the request object
reqObj.oauth_signature = hashedBaseStr;

// Launch!
rest.post(fatSecretRestUrl, {
  data: reqObj,
}).on('complete', function(data, response) {
  console.log(response);
  console.log("DATA: " + data + "\n");
});



On Monday, November 11, 2013 5:06:25 PM UTC-5, James Splaine wrote:

Shahnewaz Khan

unread,
Nov 15, 2015, 4:51:05 PM11/15/15
to FatSecret Platform API
This is awesome, I am able to get a response from the FatSecret REST endpoint, but it only works on the first try, my code is exactly as your solution.

All subsequent calls fail with 'Invalid oauth_signature', until I restart the NodeJS server, then the first one goes through and all others fail.

I checked both the TimeStamp and Nonce and they are always different for each call.

Any ideas why this is happening?

James Splaine

unread,
Nov 24, 2015, 1:47:10 AM11/24/15
to fatsecret-p...@googlegroups.com
Hey Shahnewaz,

So happy to hear that the FatSecret oath solution is (almost) working for you. 

A couple questions: Is 'Invalid oauth_signature' the entire error message?  Also, are you certain that the first call is working?

I'll have to check my code again and see if there's anything obvious...

Jay

--
You received this message because you are subscribed to a topic in the Google Groups "FatSecret Platform API" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/fatsecret-platform-api/1e4VxsuHTcA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to fatsecret-platfor...@googlegroups.com.
To post to this group, send email to fatsecret-p...@googlegroups.com.
Visit this group at http://groups.google.com/group/fatsecret-platform-api.
For more options, visit https://groups.google.com/d/optout.

Randy Hak

unread,
Dec 21, 2015, 5:09:24 PM12/21/15
to FatSecret Platform API
Hey Shahnewaz, did you solve the 'Invalid auth_signature' the second time? I think I have the same thing:

  • First I call recipes.search and it works fine.
  • Second I call recipe.get and it give me a Invalid oauth_signature. I use a new nonce and timestamp and recalculate the oauth_signature.
I use react-native to connect to fat-secret. Heres my code, maybe it will help you or someone else:

------- code -----
var CryptoJS = require('crypto-js/crypto-js');

var OAUTH_CONSUMER_KEY = '*********************************';
var SHARED_SECRET = '*********************************';
var APPLICATION = 'MyApp';

var REQUEST_METHOD = 'GET';

var OAUTH_SIGNATURE_METHOD = 'HMAC-SHA1';
var OAUTH_VERSION = '1.0';


// Optional
var FORMAT = 'json';

module.exports = {

  searchRecipes: function(SEARCH_EXPRESSION, RECIPE_TYPE, PAGE_NUMBER, MAX_RESULTS){
    var METHOD = 'recipes.search';
    var OAUTH_TIMESTAMP = new Date().getTime();
    var OAUTH_NONCE = ''+new Date().getTime();


    // Create a Signature Base String
    var REQUEST_URL_ENCODED = encodeURIComponent(REQUEST_URL);
    var NORMALISED_PARAMETERS = 'format='+FORMAT;    
    if(MAX_RESULTS != null)
      NORMALISED_PARAMETERS += '&max_results='+MAX_RESULTS;
    NORMALISED_PARAMETERS += '&method='+METHOD;
    NORMALISED_PARAMETERS += '&oauth_consumer_key='+OAUTH_CONSUMER_KEY;
    NORMALISED_PARAMETERS += '&oauth_nonce='+OAUTH_NONCE;
    NORMALISED_PARAMETERS += '&oauth_signature_method='+OAUTH_SIGNATURE_METHOD;
    NORMALISED_PARAMETERS += '&oauth_timestamp='+OAUTH_TIMESTAMP;
    NORMALISED_PARAMETERS += '&oauth_version='+OAUTH_VERSION;
    if(PAGE_NUMBER != null)
      NORMALISED_PARAMETERS += '&page_number='+PAGE_NUMBER;  
    if(RECIPE_TYPE != null)
      NORMALISED_PARAMETERS += '&recipe_type='+RECIPE_TYPE;
    if(SEARCH_EXPRESSION != null)
      NORMALISED_PARAMETERS += '&search_expression='+SEARCH_EXPRESSION;


    NORMALISED_PARAMETERS_ENCODED = encodeURIComponent(NORMALISED_PARAMETERS);

    var BASE_STRING = `${REQUEST_METHOD}&${REQUEST_URL_ENCODED}&${NORMALISED_PARAMETERS_ENCODED}`;

    // Calculate the Signature value
    SHARED_SECRET+='&'; // no user, & needed   

    var OAUTH_SIGNATURE = CryptoJS.HmacSHA1(BASE_STRING, SHARED_SECRET);
    var OAUTH_SIGNATURE_BASE64 = CryptoJS.enc.Base64.stringify(OAUTH_SIGNATURE);
    var OAUTH_SIGNATURE_BASE64_ENCODED = encodeURIComponent(OAUTH_SIGNATURE_BASE64);
    var OAUTH_REQUEST_URL = `${REQUEST_URL}?${NORMALISED_PARAMETERS}&oauth_signature=${OAUTH_SIGNATURE_BASE64_ENCODED}`;

    // Send the Request
    return fetch(OAUTH_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        console.log('responseData');
        console.log(responseData);
        return responseData;
      });
  },
  getRecipeDetails: function(RECIPE_ID){
    var METHOD = 'recipe.get';
    var OAUTH_TIMESTAMP = new Date().getTime();
    var OAUTH_NONCE = ''+new Date().getTime();

    // Create a Signature Base String
    var REQUEST_URL_ENCODED = encodeURIComponent(REQUEST_URL);
    var NORMALISED_PARAMETERS = 'format='+FORMAT;    
    NORMALISED_PARAMETERS += '&method='+METHOD;
    NORMALISED_PARAMETERS += '&oauth_consumer_key='+OAUTH_CONSUMER_KEY;
    NORMALISED_PARAMETERS += '&oauth_nonce='+OAUTH_NONCE;
    NORMALISED_PARAMETERS += '&oauth_signature_method='+OAUTH_SIGNATURE_METHOD;
    NORMALISED_PARAMETERS += '&oauth_timestamp='+OAUTH_TIMESTAMP;
    NORMALISED_PARAMETERS += '&oauth_version='+OAUTH_VERSION;
    NORMALISED_PARAMETERS += '&recipe_id='+RECIPE_ID;

    NORMALISED_PARAMETERS_ENCODED = encodeURIComponent(NORMALISED_PARAMETERS);

    var BASE_STRING = `${REQUEST_METHOD}&${REQUEST_URL_ENCODED}&${NORMALISED_PARAMETERS_ENCODED}`;

    // Calculate the Signature value
    SHARED_SECRET+='&'; // no user, & needed   

    var OAUTH_SIGNATURE = CryptoJS.HmacSHA1(BASE_STRING, SHARED_SECRET);
    var OAUTH_SIGNATURE_BASE64 = CryptoJS.enc.Base64.stringify(OAUTH_SIGNATURE);
    var OAUTH_SIGNATURE_BASE64_ENCODED = encodeURIComponent(OAUTH_SIGNATURE_BASE64);
    var OAUTH_REQUEST_URL = `${REQUEST_URL}?${NORMALISED_PARAMETERS}&oauth_signature=${OAUTH_SIGNATURE_BASE64_ENCODED}`;

    // Send the Request
    return fetch(OAUTH_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        console.log('responseData');
        console.log(responseData);
        return responseData;
      });
  }

}

Gabriel David

unread,
Jul 3, 2016, 7:01:06 PM7/3/16
to FatSecret Platform API
 just ran into an issue with Ionic where requests just all around weren't working with both POST and GET. The problem was because I was using a proxy. Using a chrome extension that force allows CORS solved the problem. This appears to only be a bug with browsers; on mobile devices everything works.

Conversely, I also just noticed that when I add format: json (pre base string concatenation) I get the invalid oauth error, though I'm sure you figured out the problem since it's been half a year lol.

Gabriel David

unread,
Jul 3, 2016, 7:01:06 PM7/3/16
to FatSecret Platform API
https://forum.ionicframework.com/t/does-cors-proxy-workaround-work-for-posts/21505

I wrote a post but it got deleted? Or something idk. 

- format: json throws that err for me, remove it and works
- using Ionic and proxying throws that error. Enable CORS (for browser only) and works


On Monday, December 21, 2015 at 4:09:24 PM UTC-6, Randy Hak wrote:

lu...@meraki-digital.com

unread,
Aug 29, 2016, 7:25:12 PM8/29/16
to FatSecret Platform API

lu...@meraki-digital.com

unread,
Aug 29, 2016, 7:25:12 PM8/29/16
to FatSecret Platform API
I found the issue with this code for anyone who wants to do multiple requests...

// construct a param=value& string and uriEncode
var paramsStr = '';
for (var i in reqObj) {
 paramsStr += "&" + i + "=" + reqObj[i];
}
// yank off that first "&"
paramsStr = paramsStr.substr(1);
var sigBaseStr='';
sigBaseStr = "POST&" + encodeURIComponent(fatSecretRestUrl) + "&"+ encodeURIComponent(paramsStr);

// no  Access Token token (there's no user .. we're just calling foods.search)
sharedSecret += "&";                         <--------------------------------------------------------------------------------------------------------------------------- Right here. If you make multiple requests with the same function, you will keep adding ampersands to the request over and over. So, you need to make this only happen in you have a user. I just took out this line and added a single ampersand onto my key.

var hashedBaseStr  = crypto.createHmac('sha1', sharedSecret).update(sigBaseStr).digest('base64');
reqObj.oauth_signature = hashedBaseStr;
rest.post(fatSecretRestUrl, {
 data: reqObj,
}).on('complete', function(data, response) {
if( reqObj.method == 'recipes.search' ) {
var returnData = [];
for(var i=0;i<data.recipes.recipe.length;i++) {
returnData.push(data.recipes.recipe[i]);
}
callback(returnData);
} else if( reqObj.method == 'recipe.get' ) {
// console.log(data);
callback( data );
}
});


On Monday, December 21, 2015 at 4:09:24 PM UTC-6, Randy Hak wrote:

Shanthi Palani

unread,
Nov 2, 2016, 7:02:46 AM11/2/16
to FatSecret Platform API
How to pass page number ?

Khairunnisa

unread,
Jun 6, 2021, 6:23:39 PM6/6/21
to FatSecret Platform API
do you have this code but in vanilla javascript version? without using any kind of javascript library
=====
Perhatian: E-mail ini (termasuk seluruh lampirannya, bila ada) hanya ditujukan kepada penerima yang tercantum di atas. Jika Anda bukan penerima yang dituju, maka Anda tidak diperkenankan untuk memanfaatkan, menyebarkan, mendistribusikan, atau menggandakan e-mail ini beserta seluruh lampirannya. Mohon kerjasamanya untuk segera memberitahukan Politeknik Negeri Jakarta di alamat email yang tercantum di atas serta menghapus e-mail ini beserta seluruh lampirannya. Semua pendapat yang ada dalam e-mail ini merupakan pendapat pribadi dari pengirim yang bersangkutan dan tidak serta merta mencerminkan pandangan  Politeknik Negeri Jakarta, kecuali telah terdapat kesepakatan antara pengirim dan penerima bahwa e-mail ini termasuk salah satu bentuk komunikasi kedinasan yang dapat diterima oleh kedua pihak.

Caution: The information enclosed in this email (and any attachments) may be legally privileged and/or confidential and is intended only for the use of the addressee(s). No addressee should forward, print, copy, or otherwise reproduce this message in any manner that would allow it to be viewed by any individual not originally listed as a recipient. If the reader of this message is not the intended recipient, you are hereby notified that any unauthorized disclosure, dissemination, distribution, copying or the taking of any action in reliance on the information herein is strictly prohibited. If you have received this communication in error, please immediately notify the sender and delete this message. Unless it is made by the authorized person, any views expressed in this message are those of the individual sender and may not necessarily reflect the views of Politeknik Negeri Jakarta.
Reply all
Reply to author
Forward
0 new messages