OAuth Authentication - Invalid signature. Node.js

314 views
Skip to first unread message

mr.b...@gmail.com

unread,
Jan 15, 2023, 9:33:56 PM1/15/23
to FatSecret Platform API
Hello there! I'm trying to implement the  OAuth Authentication, but I'm getting the "Invalid signature".
This is my code:

let query = new URLSearchParams({
oauth_consumer_key: "xxx",
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(Date.now() / 1000),
oauth_nonce: 'test',
oauth_version: '1.0',
oauth_callback: 'oob'
});

const method = 'GET';
const baseUrl = 'https://www.fatsecret.com/oauth/request_token';

const signatureBaseString = `${method}&${encodeURIComponent(baseUrl)}&${encodeURIComponent(query.toString())}`;
console.log('signature_base_string', signatureBaseString);
const secretKey = 'xxx';
const signingKey = `${secretKey}&`;


const oauth_signature = crypto.createHmac('sha1', signingKey).update(signatureBaseString).digest('base64');
let data = 'empty';
try {

const response = await fetch(`${baseUrl}?${query.toString()}&oauth_signature=${oauth_signature}`, {
method: 'GET'
});
data = await response.text();
} catch (error) {
console.log('There was an error', error);
}

What can be wrong with this implementation?

Thanks in advance!

Yuri Orlov

unread,
May 3, 2023, 6:23:32 PM5/3/23
to FatSecret Platform API
I have the same problem. 

Http code 400

seba...@fatsecret.com

unread,
May 4, 2023, 2:20:59 AM5/4/23
to FatSecret Platform API
Hi guys,
Can you pls double check all calls are made via HTTPS?
Kind regards,
The FatSecret Platform API Team

seba...@fatsecret.com

unread,
May 4, 2023, 5:09:51 AM5/4/23
to FatSecret Platform API
Hi guys,

Something else that might help:

    On FatSecret REST API - Authentication OAuth 1 https://platform.fatsecret.com/api/Default.aspx?screen=rapiauth1

Delete the spaces around the “&” sign in the string: POST & https%3A%2F%2Fplatform.fatsecret.com%2Frest%2Fserver.api & a%3Dfoo%26oauth_consumer_key%3Ddemo%26oauth_nonce%3Dabc%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D12345678%26oauth_version%3D1.0%26z%3Dbar

=> Signature fails if you include spaces around the “&” sign.

=> request_token does not work with POST, only with GET

Kind regards,
The FatSecret Platform API Team

Felipe Coury

unread,
May 7, 2023, 7:45:46 PM5/7/23
to FatSecret Platform API
I am getting the exact same error and I checked my base string over and over again: no spaces, https confirmed, and still get error 400. Any hints?

Nathan M

unread,
Mar 5, 2024, 5:40:56 PMMar 5
to FatSecret Platform API
Hi All,

     I guess my last post didn't post.  I wanted to add a class I built to handle the Oauth1.0 actions.  I worked way too long on this due to a "&" in the wrong place that I missed in the documents.

import crypto from 'crypto';
import { FS_ID, FS_OA1_API_KEY } from '$env/static/private';
import { v4 as uuidv4 } from 'uuid';

export default class FatSecretOauth1 {
     constructor(httpMethod, url, inputParameters) {
        this.requestUrl = url;
        this.httpMethod = httpMethod.toUpperCase();
        this.inputParameters = {
            ...inputParameters,
            format: 'json',
            oauth_consumer_key: FS_ID,
            oauth_nonce: uuidv4(),
            oauth_signature_method: 'HMAC-SHA1',
            oauth_timestamp: Math.floor(new Date().getTime()),
            oauth_version: '1.0'
        };
        this.paramString = this.buildRequestParameterString();
        this.signature = this.buildSignature();
        return { paramString: this.paramString, signature: this.signature };
        }

   buildSignature() {
     let method = encodeURIComponent(this.httpMethod);
     let url = encodeURIComponent(this.requestUrl);
     let params = encodeURIComponent(this.paramString);
     let signature = crypto
       .createHmac('sha1', `${FS_OA1_API_KEY}&`) //I missed the "&" after the API_KEY before hashing.
       .update(`${method}&${url}&${params}`)
       .digest()
       .toString('base64');
     return encodeURIComponent(signature);
     }

     buildRequestParameterString() {
       let params = '';
       Object.entries(this.inputParameters)
       .sort()
       .forEach((cur) => (params += `&${encodeURI(cur[0])}=${encodeURI(cur[1])}`));
       params = params.substring(1);
       return params;
     }
}


This kicked my butt for a couple days.  I finally came across a discussion on Github where uzair157 posted in 2015 (https://github.com/EugeneHoran/Android-FatSecret-REST-API/issues/2).  Their post saved my sanity, because I couldn't figure out for the life of me what was wrong.  Anyway, the class will return the parameter string and signature to add to your fetch.  The fetch will look like: 

const foodSearch = {
  method: 'foods.search.v3',  //This is the endpoint method, not the httpMethod
  search_expression: 'Hershey chocolate pie'  //Get this parameter from the endpoint documentation.
};

const searchByID = {
  method: 'food.get.v4',  //This is the endpoint method, not the httpMethod
  food_id: 27443   //Get this parameter from the endpoint documentation. 
};

let httpMethod = 'POST';

///
const result = new FatSecretOauth1(httpMethod, url, searchByID); //Call the class and add the required items.               
dfsdjsdfs
rea
const response = await fetch(`${url}?${result.paramString}&oauth_signature=${result.signature}`, {
  method: 'POST'
});

const data = await response.json();
console.log('fetch Data: ', data);


The class returns the result.paramString and result.signature.  Just add them to your fetch and send.  Thank you to all of the people on here that posted, it made working through this much easier.

Nate

Nathan M

unread,
Mar 5, 2024, 5:41:02 PMMar 5
to FatSecret Platform API
Hi Everyone,
     So I am new to the group, but wanted to contribute.  I spent the last two days struggling to figure out how to use this API.  Come to find out, my attention to detail was just garbage.  I missed in the docs where is said to add a "&" to the end of your API_SECRET before encoding the signature string.  It led me to get a 'missing parameter method" error, and then "invalid signature" error.  In order to assist the next person, I wanted to post the class I created to handle the Oauth1.0 requests:

The class takes in the httpMethod, e.g. "POST" or "GET".  It then takes in the url: "https://platform.fatsecret.com/rest/server.api'.  Last you will enter input the query parameter object, see below.

import crypto from 'crypto';
import { FS_ID, FS_OA1_API_KEY } from '$env/static/private';
import { v4 as uuidv4 } from 'uuid';

export default class FatSecretOauth1 {
constructor(httpMethod, url, inputParameters) {
this.requestUrl = url;
this.httpMethod = httpMethod.toUpperCase();
this.inputParameters = {
...inputParameters,
format: 'json',
oauth_consumer_key: FS_ID,
oauth_nonce: uuidv4(),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(new Date().getTime()),
oauth_version: '1.0'
};
this.paramString = this.buildRequestParameterString();
this.signature = this.buildSignature();
return { paramString: this.paramString, signature: this.signature };
}

buildSignature() {
let method = encodeURIComponent(this.httpMethod);
let url = encodeURIComponent(this.requestUrl);
let params = encodeURIComponent(this.paramString);
let signature = crypto
.createHmac('sha1', `${FS_OA1_API_KEY}&`) //This "&" at the end cost me hours.
.update(`${method}&${url}&${params}`)
.digest()
.toString('base64');
return encodeURIComponent(signature);
}

buildRequestParameterString() {
let params = '';
Object.entries(this.inputParameters)
.sort()
.forEach((cur) => (params += `&${encodeURI(cur[0])}=${encodeURI(cur[1])}`));
params = params.substring(1);
return params;
}
}


My error was on the line ".createHmac('sha1', `${FS_OA1_API_KEY}&`). That little "&" alluded me until I was reading: Uzair157's comment at https://github.com/EugeneHoran/Android-FatSecret-REST-API/issues/2.  That fixed it and I was able to cleanup the function.  I removed the fetch from the class to make it easier for people to use the class.  The fetch portion is:

const foodSearch = {
method: 'foods.search.v3',
search_expression: 'Hershey chocolate pie'
};

const searchByID = {
method: 'food.get.v4',
food_id: 27443
};

let httpMethod = 'POST';

const result = new FatSecretOauth1(httpMethod, url, searchByID);

const response = await fetch(`${url}?${result.paramString}&oauth_signature=${result.signature}`, {
method: 'POST'
});

const data = await response.json();
console.log('fetch Data: ', data);


In using the fetch, you just add the fatSecret method, e.g. 'food.get.v4' or 'foods.search.v3' as the object method, then add the required parameters for the endpoint per the FatSecret documentation.
For example: 
- the food.get.v4 endpoint requires: food_id: value 
- the food.search.v3 endpoint requires: search_expression: <value>

You will see that I added the format to the class, which was only because I want all results in JSON and didn't want to have to add format to every query individually.  I sincerely hope that this post will save people in the future a lot of time.  Also, thank you to all of the people who posted before me, I was able to glean a lot from all of your work.

Nate

On Sunday, May 7, 2023 at 4:45:46 PM UTC-7 Felipe Coury wrote:

seba...@fatsecret.com

unread,
Mar 5, 2024, 5:43:31 PMMar 5
to FatSecret Platform API
Hi Nate,
Thanks for reaching out and providing all the updated information. 
Great to hear you got it all working. The insights and lessons above are super helpful for other developers and we appreciate your input.
Keep up the great work.
The FatSecret Platform API Team

Reply all
Reply to author
Forward
0 new messages