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: