Wednesday, December 30, 2015

Twitter API integration with Salesforce :- User Authentication


             Sometimes we have to pull tweets from Twitter or post status to Twitter from Salesforce through Apex callouts. To interact with Twitter API on behalf of a user, an authentication has to be implemented. Twitter provides different types of authentication mechanisms. Here we can use  3-legged authorization.  The implementation includes the following steps.
  1. Register a Twitter Application
  2. Obtain the request token from Twitter
  3. Authenticate with User
  4. Obtain the Access token.

  1. Register a Twitter Application

            Log on to https://apps.twitter.com/ and go for Create new App and fill the necessary details. We can provide Website & Callback URL as our Visualforce page URL.

       

        After successful registration, we will get a consumer key & a consumer secret.

       


   2. Obtain the Request Token from Twitter


         To get the request token, we have to make a POST request to the Twitter API. For every API request, Twitter needs an Authorization header (API documentation). The header includes the following parameters
  • oauth_consumer_key -  The consumer key from Twitter app
  • oauth_nonce - An unique string to identify the request
  • oauth_signature -  A value which is generated by all of the request parameters and two secret values through a signing algorithm.
  • oauth_signature_method -  The signature method to generate oauth_signature. eg: HMAC-SHA1
  • oauth_timestamp - The number of seconds since the Unix epoch at the point the request is generated
  • oauth_version - Always be 1.0 for any request.
  • oauth_token - if available from Twitter (optional).

The API request as per the API documentation:

    Request Method: POST
    End point: https://api.twitter.com/oauth/request_token
    Parameters: oauth_callback = "the Visulaforce page URL"
    Header: Authorization header generated with the above values.

Salesforce settings

         Before going to callouts, we have to add the Twitter API URL (https://api.twitter.com) in remote site settings.  And we have to declare some constants in apex class.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    final String oauthVersion = '1.0';
    final String oauthConsumerKey = '<YOUR KEY>';
    final String oauthConsumerSecret = '<YOUR SECRET>';
    final String baseUrl = 'https://api.twitter.com';
    final String oauthSignatureMethod = 'HMAC-SHA1';
    final String oauth_callback = '<YOUR VF PAGE URL>';
    
    String oauthTimestamp;
    String oauthNonce;
    String oauthToken;
    String oauthTokenSecret;
    String accessToken;
    String accessTokenSecret;

2.1 Generating oauth_timestamp in apex code


1
2
3
4
private void getTimeStamp(){
        DateTime dateTimeNow = dateTime.now();
        this.oauthTimestamp = ''+(dateTimeNow.getTime()/1000);
    }


2.2 Generating oauth_nonce in apex code



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private  void generateNounce() {
        final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
        String randStr = '';
        
        while (randStr.length() < 32) {
           Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
           randStr += chars.substring(idx, idx+1);
        }
        this.oauthNonce =  EncodingUtil.base64Encode(Blob.valueOf(randStr)).remove('=');
    }


2.3  Generating oauth_signature  in apex code


             As per the API documentation, we have to collect all the parameters used in the request along with the oauth parameters and encrypt them by a signing algorithm. Also we have to create a signing key from consumer secret. We can use the Apex Crypto class for HMAC-SHA1 encoding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private String generateSignature(String httpMethod, String baseUrl, Map<String, String> params){
              
           String encodedString = '';
           Map<String, String> encodeParams = new Map<String, String>();       
           encodeParams.putAll(params);
           encodeParams.put('oauth_nonce', this.oauthNonce);
           encodeParams.put('oauth_signature_method', this.oauthSignatureMethod);
           encodeParams.put('oauth_timestamp', this.oauthTimestamp);
           encodeParams.put('oauth_consumer_key', this.oauthConsumerKey);
           encodeParams.put('oauth_version', this.oauthVersion);
           
           List<String> keyList = New List<String>();
           keyList.addAll(encodeParams.keySet());
           keyList.sort();
           
           for(String key: keyList){
               encodedString +=  EncodingUtil.urlEncode(key,'UTF-8') + '=' + EncodingUtil.urlEncode(encodeParams.get(key),'UTF-8') + '&';
           }
           encodedString = encodedString.removeEnd('&');
            
           String baseString = httpMethod.toUpperCase() + '&' + EncodingUtil.urlEncode(baseUrl,'UTF-8') + '&' + EncodingUtil.urlEncode(encodedString,'UTF-8');
           String signingKey = EncodingUtil.urlEncode(this.oauthConsumerSecret,'UTF-8') + '&';
           if(params.containsKey('oauth_token') && String.isNotBlank(this.oauthTokenSecret)){
               signingKey += EncodingUtil.urlEncode(this.oauthTokenSecret,'UTF-8');
           }   
           
           Blob data = Crypto.generateMac('hmacSHA1', Blob.valueOf(baseString), Blob.valueOf(signingKey));
           String signature =  EncodingUtil.base64Encode(data);
           return signature;
}  


2.4 Generating the authorization header in apex code



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private String generateAuthHeader(Map<String, String> params){
           
           Map<String, String> authParams = new Map<String, String>();
           authParams.putAll(params);
           authParams.put('oauth_consumer_key', this.oauthConsumerKey);
           authParams.put('oauth_signature_method', this.oauthSignatureMethod);
           authParams.put('oauth_timestamp', this.oauthTimestamp);
           authParams.put('oauth_nonce', this.oauthNonce);
           authParams .put('oauth_version', this.oauthVersion);
           
           List<String> keyList = New List<String>();
           keyList.addAll(authParams .keySet());
           keyList.sort();
           String OathString = '';    
           for(String key: keyList){
              OathString += EncodingUtil.urlEncode(key,'UTF-8') + '=' + '"' + EncodingUtil.urlEncode(authParams.get(key),'UTF-8') + '"' + ', '; 
           }
           OathString = 'OAuth ' + OathString.removeEnd(', ');
           return  OathString ;
    
} 

2.4 Apex callout to get the Request Token


      For this request we don't have the oauth_token & oauth_token_secret. We can use rest of the parameters for generating signature and the header.
           
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void getAuthToken(){
    
         String requestUrl = this.baseUrl + '/oauth/request_token';
         String requestMethod = 'POST';
         
         this.getTimeStamp();
         this.generateNounce();
         
         Map<String, String> params = new Map<String, String>();
         params.put('oauth_callback', this.oauth_callback);         
         String authSignature = this.generateSignature(requestMethod, requestUrl, params);
         
         params = new Map<String, String>();
         params .put('oauth_callback',this.oauth_callback);
         params .put('oauth_signature', authSignature);
          
          HttpRequest request = new HttpRequest();
          request.setHeader('Authorization', this.generateAuthHeader(params));
          request.setMethod(requestMethod);
          request.setEndpoint(requestUrl);
          HttpResponse response = new HttpResponse();
          this.oauthToken = '';
          Http  http = new Http();
          try{
              response = http.send(request);
              String responseBody = response.getBody();
              this.oauthToken = responseBody.substringBefore('&').substringAfter('=');
              this.oauthTokenSecret = detail.substringAfter('&').substringBetween('=','&');
              ApexPages.currentPage().setCookies(new Cookie[]{new Cookie('TSecret', oauthTokenSecret, null, -1, false)});
          }catch(Exception e){
              system.debug(e.getMessage());
          }
          
          return null;
    }  

We will get a responds with oauth_token & oauth_token_secret as below:

oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM&oauth_callback_confirmed=true

          After getting oauth_token & oauth_token_secret we have to go for user authentication.  After user authentication we need this oauth_token_secret for next request. So we need to store the oauth_token_secret  temporally. Here we can save this in a cookie.


  3. Authenticate with User


    As per the documentation, we have to call the authentication URL in browser with the oauth_ token obtained from previous step as a parameter. 


1
2
3
4
5
6
public PageReference authenticate(){
          
         String requestUrl = baseUrl + '/oauth/authenticate?oauth_token=' + this.oauthtoken + '&force_login=true';         
         return  new PageReference(requestUrl).setRedirect(true);
        
     }


This will redirect to the Twitter login page. After successful user login, Twitter redirects to our Visualforce page  with oauth_token & oauth_verifier as URL parameters.

4. Obtain the Access Token


After getting the oauth_varifier,  we have to obtain the access_token by another POST request.

The API request as per the API documentation:

    Request Method: POST
    End point: https://api.twitter.com/oauth/access_token
    Parameters: oauth_verifier  = The value we collected on previous step.
    Header: Authorization header

            To generate the header, follow the same steps for  Obtain the request token described above. So we have to generate new time_stamp, new nonce, new signature with new parameters and  new signing key as a combination of consumer_secret and oauth_token_secret (already saved in cookies). And we have to include the oauth_token for both signature and header.

The corresponding apex code:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public void  getAccesToken(){
 
       this.oauthtoken = ApexPages.currentPage().getparameters().get('oauth_token');
       
       if(String.isNotBlank(this.oauthtoken)){
           
            String twitterId;
            Cookie counter = ApexPages.currentPage().getCookies().get('TSecret');
            if(counter != null) {
                this.oauthTokenSecret = counter.getValue();
                ApexPages.currentPage().setCookies(new Cookie[]{new Cookie('TSecret', '', null, -1, false)});
            }
    
           String requestUrl = this.baseUrl + '/oauth/access_token';
           String httpMethod = 'POST';        
           String oauthVerifier = ApexPages.currentPage().getparameters().get('oauth_verifier');
           
           this.getTimeStamb();
           this.generateNounce();
           
           Map<String, String> params = new Map<String, String>();
           params.put('oauth_token', this.oauthToken);
           params.put('oauth_verifier', oauthVerifier);           
           String authSignature = this.generateSignature(httpMethod, requestUrl, params);
           
           params = new Map<String, String>();
           params.put('oauth_token',this.oauthtoken);
           params.put('oauth_signature',authSignature);
           
           HttpRequest request = new HttpRequest();
           HttpResponse response = new HttpResponse();
           Http  http = new Http();
           request.setEndPoint(requestUrl);
           request.setMethod(httpMethod);
           request.setHeader('Authorization', this.generateAuthHeader(params));
           request.setBody('oauth_verifier='+EncodingUtil.urlEncode(oauthVerifier, 'UTF-8'));
           try{
              response = http.send(request);
              String responseBody = response.getBody();
              this.oauthToken = responseBody.substringBetween('oauth_token=', '&');
              this.oauthTokenSecret = responseBody.substringBetween('oauth_token_secret=', '&');
              twitterId = responseBody.substringBetween('user_id=', '&');
              
              detail = twitterId;
          }catch(Exception e){
              system.debug(e.getMessage());
          }
          
      }
       
}

  This will return the access_token, access_token_secret and the user details.

oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY&oauth_token_secret=2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU&user_id=6253282&screen_name=twitterapi

Using this token we can interact with the Twitter API to post or get tweets.

References:


13 comments:

  1. after completing all these steps,how can i get the collection of retweets using tweeter api.
    please reply

    ReplyDelete
  2. Check here: https://dev.twitter.com/rest/reference/get/statuses/retweets/%3Aid

    ReplyDelete
  3. Hi Riyas,

    I followed above steps and i got oauth_Token and oauth_Token_Secret successfully. Now i want to use/consume https://api.twitter.com/1.1/statuses/update.json to post from salesforce to twitter. I am getting following error:

    response - System.HttpResponse[Status=Authorization Required, StatusCode=401]

    response.getBody() - {"errors":[{"code":32,"message":"Could not authenticate you."}]}

    OauthString = OAuth oauth_consumer_key="ILFzs0lcUiHwJsbpuWU4tTdk3",oauth_nonce="RElxMGNiWFJMOGp2V2VRcHB5aU96REhqYnVnbHZXc2o",oauth_signature="2aWbL3JASeSgNRaDQ%2BgLNXpH8dA%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1466659830",oauth_token="743383838253002753-L4gdS3UCxKZVGTtQ0SHxSMlVtJREIBk",oauth_version="1.0"

    could you please help on this?

    Thanks in advance

    ReplyDelete
  4. @Rahul- you have to include all the post parameters So please add your update json also for signature generation

    ReplyDelete
  5. @Riyas: Thanks for suggestion. Now its working fine.

    ReplyDelete
    Replies
    1. Hi @Rahul Chaudhari, I'm also trying to post the tweet after all the above process and i'm facing same System.HttpResponse[Status=Authorization Required, StatusCode=401] error. can you help me out here.

      Delete
  6. Hi @Rahul-I am also facing same problem like you. Could you please share your code to post the status.

    Thanks for your help.

    ReplyDelete
  7. Hi Riyas,

    I am new to salesforce and i am trying to use 1.1/statuses/home_timeline.json but i am getting the same error code as Rahul.

    i wanted to know what parameters should i pass to get signature?

    the oauthstring is
    oauth_consumer_key="eGUoMQPlhp96GVRotc2URnkVn", oauth_nonce="YTY1MTNZTjBkV2VaT1l1UUlTV254S05aUURQeDc2SkI", oauth_signature="lmR9mnDijcotQJ%2BL7XGDQuYtiLc%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1475202329", oauth_token="773816383918448640-LVxiGlKFYkF8dJeGJLl2XW1RV86zJ00", oauth_version="1.0"

    can you please help me on this?

    thanks in advance.

    ReplyDelete
  8. I am getting the error as Error: Compile Error: Variable does not exist: detail at line.
    Please reply why I am getting this one?

    ReplyDelete
  9. Getting error as :Whoa there!
    There is no request token for this page. That's the special key we need from applications asking to use your Twitter account. Please go back to the site or application that sent you here and try again; it was probably just a mistake. Any idea about the issue resolution.

    ReplyDelete
  10. Getting error as :Whoa there!
    There is no request token for this page. That's the special key we need from applications asking to use your Twitter account. Please go back to the site or application that sent you here and try again; it was probably just a mistake. Any idea about the issue resolution.

    ReplyDelete
  11. To post a tweet after getting oauth_Token and oauth_Token_Secret successfully, please find the below extra steps.

    1. set request url to https://api.twitter.com/1.1/statuses/update.json
    2. send the parameters of this post request as param to generate a signature. Like a 'status' which is the message to be posted.
    3. Don't send the oauth_verifier as param to generate the signature.
    4. Don't set the oauth_verifier as a body of http request like below.
    request.setBody('oauth_verifier='+EncodingUtil.urlEncode(oauthVerifier, 'UTF-8'));

    ReplyDelete
  12. It is a good site post without fail. Not too many people would actually, the way you just did. I am impressed that there is so much information about this subject that has been uncovered and you’ve defeated yourself this time, with so much quality. Good Works! Expert secrets

    ReplyDelete