2

I have some code generating OAuth signature and sending request to NetSuite. I reviewed all the parameters I send to NetSuite with their developers and they told me that I have some issue with oauth_signature.

I will really appreciate if you take a look and tell me what I am doing wrong.

public String timestamp {get; set;} //seconds since unix epoch
public String nonce {get; set;} //random number for making request unique
public Map<String, String> oauth_params {get; set;} //store oauth params for signature generation   
public OAuthService__c ns_oauth {get; set;} //oauth data for ns -> salesforce

public String GenerateSignature(String httpMethod) {

    ns_oauth = [select Consumer_Key__c, Consumer_Secret__c, Authorization_URL__c, Realm__c, 
                           (select token__c, secret__c from Tokens__r)
                      from OAuthService__c
                     where Name = 'SalesForce' limit 1];

    timestamp = String.valueOf(DateTime.now().getTime()/1000); //seconds since unix epoch
    nonce = String.valueOf(Crypto.getRandomLong()); //random number

    oauth_params = new Map<String, String>();

    if(ns_oauth != null) { //store parameters for signature creating
        oauth_params.put('oauth_version', '1.0');
        oauth_params.put('oauth_nonce', nonce);
        oauth_params.put('oauth_timestamp', timestamp);
        oauth_params.put('oauth_consumer_key', ns_oauth.Consumer_Key__c);
        oauth_params.put('oauth_token', ns_oauth.Tokens__r[0].token__c);
    }

    String sig = normalizeUrl(ns_oauth.Authorization_URL__c);  //signature starts with normalized url (with port number)
    Blob sigMac; //hash of signature

    sig = httpMethod.toUpperCase() + '&' + EncodingUtil.urlEncode(sig, 'UTF-8');

    //sort parameters  before appending to signature 
    List<string> sortParams = new List<string>(oauth_params.keySet());
    sortParams.sort();

    //append all the params for signature
    for(String param_key : oauth_params.keySet()) {
        sig += '&' + param_key + '=' + oauth_params.get(param_key);
    }

    //compute HASH using SHA-1 algorithm
    //where key is acombination of concumer secret and token secret

    String hash_key = ns_oauth.Consumer_Secret__c + '&' + ns_oauth.Tokens__r[0].secret__c;

    sigMac = Crypto.generateMac('HmacSHA1', Blob.valueOf(sig), Blob.valueOf(hash_key));

    return EncodingUtil.urlEncode(EncodingUtil.base64Encode(sigMac), 'UTF-8');

}

I get in response following:

{"error" : {"code" : "INVALID_LOGIN_ATTEMPT", "message" : "Invalid login attempt."}}
2
  • @VladmirG: I think the issue is with using the encodeUtil.urlEncode. I had same problem with using this for generating a JWT token. You basically need to get the EncodingUtil.base64Encode(sigMac) and remove the padding '=' and replace + with -(hyphen) and / with _ (underscore)
    – Sam
    Oct 29, 2015 at 13:15
  • @Mugambo, thank you. Unfortunately it does not help
    – VladimirG
    Oct 29, 2015 at 13:56

1 Answer 1

2

It looks like the most likely problem is that you need to URL encode the URL parameters (oauth_params in your code) before sorting and concatenating them. Here is some working OAuth 1.0a code from the old 'OAuth Playground' for Salesforce - you'll need to adapt it for your situation:

private 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;
}

private 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.
    return req.getMethod().toUpperCase()+ '&' + 
        EncodingUtil.urlEncode(host, 'UTF-8') + '&' +
        EncodingUtil.urlEncode(s, 'UTF-8');
}

public void sign(HttpRequest req) {
    nonce = String.valueOf(Crypto.getRandomLong());
    timestamp = String.valueOf(DateTime.now().getTime()/1000);

    refreshParameters();

    String s = createBaseString(parameters, req);

    System.debug('Signature base string: '+s);

    Blob sig = Crypto.generateMac('HmacSHA1', Blob.valueOf(s), 
                   Blob.valueOf(consumerSecret+'&'+
                                (tokenSecret!=null ? tokenSecret : '')));
    signature = EncodingUtil.urlEncode(EncodingUtil.base64encode(sig), 'UTF-8');
    System.debug('Signature: '+signature);

    String header = 'OAuth ';
    for (String key : parameters.keySet()) {
        header = header + key + '="'+parameters.get(key)+'", ';
    }
    header = header + 'oauth_signature="'+signature+'"';
    System.debug('Authorization: '+header);
    req.setHeader('Authorization',header);
}
2
  • I got the same problem for connect to the NetSuite even I totally adopted your code. gist.github.com/surjikal/7539745. can you give me more advise? Thank you.
    – Jen
    Oct 13, 2016 at 9:45
  • Hi i am currently facing the same issue and i used the code provided by metadaddy but still i am getting Invalid Signature Error in netsuite. One thing i found is now the signature method is changed to HMAC-SHA256 instead of old HMAC-SHA1. Please let me know what can be done. If you want i can post my code here, Sep 18, 2020 at 7:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .