I have a client who has built an integration from ...
# integrations
k
I have a client who has built an integration from a SalesForce App to a NetSuite Restlet using OAuth 1.0. Occasionally, the developer, while testing in the UI of SFDC which submits POST requests to a restlet runs into a 403 error. For months, we have been trying to troubleshoot this with no answers. I have checked the Authentication Header in Postman and sort of honed in on the timestamp portion of the header. If I increment the timestamp of an authentication header which failed in a previous request, it works. The developer has logged each and every request along with the timestamp and none of the requests are reusing the same timestamp. It has to be highly improbable another integration is generating the exact same header signature at the same time or previous to any of these requests coming from SFDC. Ideally, responses to this post would provide some alternative troubleshooting steps before I and this developer decide to move over to OAuth 2.0.
a
Have you checked the login audit trail to see what the error exactly is?
k
Yes. The error is simply, INVALID LOGIN
v
How are you creating a nonce?
k
That I'm not sure of. The developer did not share her details with me. All of the headers she provided me are unique. I took them and tested them in Postman and changed the values of the nonce but they still failed. It was only when I changed the timestamp that they worked.
She did build a retry so if the initial request fails, she creates and signs a newly generated header. That has helped but now a few of the retries are failing for the same reason.
b
TBA and the Login Audit Trail is where you learn how to see the details
that said, its extra common for intermittent random looking failures to be a failure to encode the signature
which causes signature related failures whenever the signature contain a reserved character like a +
k
The Authorization header is made up of a base-64, hashed, HMAC-SHA256 encrypted "base string". In this base string, you have to change the timestamp and nonce over time (I change mine for every call). The base string includes these two values (timestamp/nonce). So, when you change them, you have to calculate a new "signature" (from the encryption above). The way the base string is constructed is extremely specific (there's an Internet RFC about it for OAuth 1.0/a). Postman has the logic to build this encrypted string built in - but they don't share the logic. If someone wants to reach out to me, I can share some code that will work.
m
@Kurt Dicus: I am able to make OAuth 1.0 connection. However, sometimes it work perfectly and sometime it failed with "InvalidSignature" error. I don't see any pattern here.
Copy code
const crypto = require('crypto');
const axios = require('axios');

const credentials = require('./secret').credentials;

const ACCOUNT_ID = '12345_SB1';
const CONSUMER_KEY = credentials.CONSUMER_KEY;
const CONSUMER_SECRET = credentials.CONSUMER_SECRET;
const TOKEN = credentials.TOKEN;
const TOKEN_SECRET = credentials.TOKEN_SECRET;

const timestamp = Math.floor(Date.now() / 1000);
const nonce = (() => {

    let nonce = ''
    const alphaNum = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    for (let index = 20; index > 0; index--) {
        nonce += alphaNum.charAt(Math.floor(Math.random() * alphaNum.length));
    }
    return nonce;
})();

console.log('timestamp:: ' + timestamp);
console.log('nonce:: ' + nonce);

const SIGNATURE_METHOD = 'HMAC-SHA256';
const urlParams = {
    limit: 5,
};
const reqParams = {
    ...{
        oauth_consumer_key: CONSUMER_KEY,
        oauth_nonce: nonce,
        oauth_signature_method: SIGNATURE_METHOD,
        oauth_timestamp: timestamp,
        oauth_token: TOKEN,
        oauth_version: '1.0'
    },
    ...urlParams
};

const encodeRFC3986URIComponent = (str) => {
    return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,);
}

const METHOD = 'GET';
const baseString = ((METHOD, url, reqParams) => {

    let strReqParams = '';
    const arrReqParamKeys = Object.keys(reqParams).sort();
    for (let index = 0; index < arrReqParamKeys.length; index++) {

        const name = arrReqParamKeys[index];
        const value = reqParams[name];

        if (index < arrReqParamKeys.length - 1) {
            strReqParams += name + '=' + value + '&';
        } else {
            strReqParams += name + '=' + value;
        }
    }
    return METHOD + '&' + encodeRFC3986URIComponent(url) + '&' + encodeRFC3986URIComponent(strReqParams);
})(METHOD, '<https://12345-sb1.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder>', reqParams);

const key = encodeRFC3986URIComponent(CONSUMER_SECRET) + '&' + encodeRFC3986URIComponent(TOKEN_SECRET);
console.log('\nbaseString:: ' + baseString);

const signature = crypto.createHmac('sha256', key)
    .update(baseString)
    .digest('base64');
console.log('\nsignature:: ' + signature);

axios.get('<https://12345-sb1.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder?limit=5>',
    {
        headers: {
            Authorization: `OAuth realm="${ACCOUNT_ID}",oauth_consumer_key="${CONSUMER_KEY}",oauth_token="${TOKEN}",oauth_signature_method="${SIGNATURE_METHOD}",oauth_timestamp="${timestamp}",oauth_nonce="${nonce}",oauth_version="1.0",oauth_signature="${signature}"`,
            Accept: '*/*',
            "Cache-Control": "no-cache",
            Connection: "keep-alive"
        }
    }
).then(response => {
    console.log(response.data);
}).catch(error => {
    console.log(error);
});
b
as mentioned above, this is a failure to encode the oauth parameters as described in the oauth 1.0 rfc
although in your specific case, you should be making the effort to use something like oauth-1.0a or axios-oauth-1.0a
m
Was able to solve my issue with the help of @Kurt Dicus. Just used encodeURIComponent() with my signature and it is working perfectly now. Code correction, const signature = encodeURIComponent(crypto.createHmac('sha256', key) .update(baseString) .digest('base64'));
c
@Kurt Dicus does adding a new signature with every call also solve pagination issues? as I keep having issues with implementing this, It works fine in postman but not in my script.
k
Explain more what you mean about pagination issues? Each call needs a new signature calculation. Have you gotten anything to work outside of Postman? If you can give me your email address, I can send you a working code block you can start with.
c
Yes I’ve gotten the first request to work outside postman but i get an InvalidSignature error when I follow the pagination links from the initial response body. even with creating a new nonce and timestamp for the sequential request.
@Kurt Dicus I’ve just send you a PM with my email address. thanks in advance.