RE: request to netsuite RESTlets response Invalid Login Attemp-Invalid Signature

I am accessing a RESTlets resource in netsuite via Token Based Authentication (TBA).

When I send my request in postman, the resource responds OK Status 200.

request to netsuite RESTlets response Invalid Login Attemp-Invalid Signature

But when I want to access from my Apex code (Salesforce).
response Invalid Login attempting Status 403.



global class Acceso_TBA_Netsuite {
    global static String accederRecurso(){
        String cunsomer_key = ‘c97bacb90cac9a4ffecce5cbe1a1b49cc8’;
        String cunsomer_secret = ‘05895369cbfe5f01d90ac9d07d3f40’;
        String token_id = ‘5fc37af11ba12f7f0e4198085ec2edeae2db8f’;
        String token_secret = ‘e3cb7c87df25f001de588aa7980c66a7’;
        Http http = new Http();
        HttpRequest req = new HttpRequest();
        req.setMethod(‘GET’);
        req.setEndpoint(‘https://246-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=719&deploy=1’);
        req = signRequest(req,cunsomer_key,token_id,cunsomer_secret,token_secret);
        HTTPResponse resp = http.send(req);
        System.debug(‘DATOS DE RESPUESTA: ‘+resp.getBody());
        return ”;
    }
    public static HttpRequest signRequest(HttpRequest req, String consumerKey, String oauthToken, String consumerSecret,String tokenSecret) {
        String nonce     = String.valueOf(Crypto.getRandomLong());
        String timestamp = String.valueOf(DateTime.now().getTime() / 1000);
        Map<String,String> parameters = new Map<String,String>();
        parameters.put(‘realm’,’246_SB1′);
        parameters.put(‘oauth_consumer_key’, consumerKey);
        parameters.put(‘oauth_nonce’,nonce);
        parameters.put(‘oauth_signature_method’,’HMAC-SHA256′);
        parameters.put(‘oauth_timestamp’,timestamp);
        parameters.put(‘oauth_token’,oauthToken);
        parameters.put(‘oauth_version’, ‘1.0’);
        String signature = generarSignature(req,consumerSecret,tokenSecret,parameters);
        System.debug(‘SIGNATURE ———————————->>> ‘+signature);
        String header = generateHeader(signature, parameters);
        req.setHeader(‘authorization’, header);
        System.debug(‘HEADER— ‘+req.getHeader(‘authorization’));
        return req;
    }
    private static String generateHeader(String signature, Map<String,String> parameters) {
        String header = ‘OAuth ‘;
        for (String key : parameters.keySet()) {
            header = header+key+’=”‘+parameters.get(key)+'”, ‘;
        }
        //return header.substring(0,header.length()-2);//+’oauth_signature=”‘+signature+'”‘;
        return header+’oauth_signature=”‘+signature+'”‘;
    }
    private static String generarSignature(HttpRequest req, String consumerSecret,String tokenSecret, Map<String,String> parameters) {
        String baseString = createBaseString(parameters,req);
        String encodedSecret = EncodingUtil.urlEncode(consumerSecret,’UTF-8′)+’&’+
                               EncodingUtil.urlEncode(tokenSecret,’UTF-8′);
        //System.debug(‘ENCODED SECRET: ‘+encodedSecret);
        Blob sig = Crypto.generateMac(
            ‘HmacSHA256’,
            Blob.valueOf(baseString),
            Blob.valueOf(encodedSecret)
        );
        return EncodingUtil.urlEncode(EncodingUtil.base64encode(sig), ‘UTF-8’);
        //return EncodingUtil.base64encode(sig);
    }
    private static String createBaseString(Map<String,String> oauthParams, HttpRequest req) {
        Map<String,String> p = oauthParams.clone();
        if(req.getMethod().equalsIgnoreCase(‘post’) && req.getBody()!=null &&
                req.getHeader(‘Content-Type’)==’application/x-www-form-urlencoded’) {
            p.putAll(getUrlParams(req.getBody()));
        }
        String host = req.getEndpoint();
        Integer n = host.indexOf(‘?’);
        if(n>-1) {
            p.putAll(getUrlParams(host.substring(n+1)));
            host = host.substring(0,n);
        }
        List<String> keys = new List<String>();
        keys.addAll(p.keySet());
        keys.sort();
        String s = keys.get(0)+’=’+p.get(keys.get(0));
        for(Integer i=1;i<keys.size();i++) {
            s = s + ‘&’ + keys.get(i)+’=’+p.get(keys.get(i));
        }
        // According to OAuth spec, host string should be lowercased, but Google and LinkedIn
        // both expect that case is preserved.
        String urlBase=req.getMethod().toUpperCase()+ ‘&’ +
                        EncodingUtil.urlEncode(host, ‘UTF-8’) + ‘&’ +
                        EncodingUtil.urlEncode(s, ‘UTF-8’);
        System.debug(‘>>URL ‘+urlBase);
        return urlBase;
    }
    private static Map<String,String> getUrlParams(String value) {
        Map<String,String> res = new Map<String,String>();
        if(value==null || value==”) {
            return res;
        }
        for(String s : value.split(‘&’)) {
            System.debug(‘<<<<<<<<<<<<<<<<<<<<<<<<<<<<< getUrlParams: ‘+s);
            List<String> kv = s.split(‘=’);
            if(kv.size()>1) {
                // RFC 5849 section 3.4.1.3.1 and 3.4.1.3.2 specify that parameter names
                // and values are decoded then encoded before being sorted and concatenated
                // Section 3.6 specifies that space must be encoded as %20 and not +
                String encName = EncodingUtil.urlEncode(EncodingUtil.urlDecode(kv[0], ‘UTF-8’), ‘UTF-8’).replace(‘+’,’%20′);
                String encValue = EncodingUtil.urlEncode(EncodingUtil.urlDecode(kv[1], ‘UTF-8’), ‘UTF-8’).replace(‘+’,’%20′);
                System.debug(‘getUrlParams:  -> ‘+encName+’,’+encValue);
                res.put(encName,encValue);
            }
        }
        return res;
    }
}

and in netsuite it shows invalid signature.

request to netsuite RESTlets response Invalid Login Attemp-Invalid Signature

 

I think it is due to the encode but I changed this and it still does not work.

Can someone help me please.

salejan Rookie Asked on February 26, 2021 in Administration.

Please remove your secrets from the post.

on February 26, 2021.

I removed characters from the secrets and tokens, so they are incomplete values

on February 26, 2021.
Add Comment
1 Answers

It is a huge pain writing these things without starting from a working implementation. Im not sure if you can find OAuth for Apex, but you probably want to start at least at a java implementation like Signpost. Troubleshoot Token-based Authentication (TBA) is the best NetSuite specific resource.

I can start listing things that I see wrong, but no promises that I see them all

  • The realm is not used as a parameter for the base string. you should add it to your oauth parameters after you have generated the base string.
  • You need to percent encode your oauth parameters while generating the header. You won’t need to for the keys, since they dont have reserved characters, you do need to do it for the values. Importantly, this will mean that your signature will be percent encoded.
  • While restlets dont accept form posts, you still need to get the query parameters from the url to be used as parameters for your base string. Same for all the other methods restlets accept
  • Although I don’t think its an issue for your inputs, you are supposed to lowercase the url’s scheme and hostname.
  • You need to percent encode both the key and the value of the parameters while generating the request parameters (your s String). This is complicated in that you preemptively percent encode your decoded url query parameters, I heavily recommend you do not do this.
  • You probably also want to make sure your percent encoding is correct. The percent encoding you will be doing thorughout uses %20 for spaces instead of +. Some encoding utilities use percent encoding as used in form posts, which uses + for spaces.

For extra emphasis, you will be percent encoding in 4 different places. Twice in your base string: once while generating your request parameters key and values, and again while combining the method, uri, and the request parameters. This will mean that your request parameters will be percent encoded twice. The third time you do percent encoding will be while generating the parameters used for the header. Fourth is for your consumer secret and token secret, though it is unnecessary the vast majority of the time, nobody generates unfriendly secrets

Intermediate Answered on March 1, 2021.
Add Comment

Your Answer

By posting your answer, you agree to the privacy policy and terms of service.