Wednesday, August 15, 2012

REST ATHENTICATION AND SIGN REQUEST USING OAUTH

In this post I'm going to explain a little how to use OAuth to Authenticate for using a REST API. In former post we used the twitter API to make a REST client application in android, but without authenticate. We need authentication to make some request, like send a tweet.

There are some ways to authenticate but Twitter uses Oauth version 1. Using OAuth we don't need to store any user/password. The user allows our app to make some things in his/her account. And our app delegates the authentication and authorization to a trusted location (in this case Twitter). Our app just has to store two tokens and sign each request to the API. The authorization has a life time in OAuth, but Twitter authorizations are unlimited, but, of course, the user can always revoke the using twitter web page.

To help us with all the authorization flow with OAuth we are going to use signpost library. 
So we will need to add signpost-commons-http4-1.2.1.2.jar (cos we are going to work with apache libraries in android) and signpost-core1.2.1.2.jar.

So the flow to authorize an application using signpost is:
  • First we need to create the twitter application on the twitter developers site. Once created we should have some information:
  • Consumer key
  • Consumer secret
  • Request token URL
  • Authorize URL
  • Access Token URL
  • Callback URL   (is the URL to redirect once authorized, but don' worry about that, use what you want, we will pass another through the flow)
  • With the key and the secret we can create an OAuthConsumer object wich will represent our application. With the URLs we can create an OAuthProvider object which will represent Twitter in this case.
  • With these two objects we can get retrieve a request token. This is the URL where the user has to authenticate and authorizate. 
  • Using that URL we have to pop the browser and wait until the user authorize the app.
  • Then we will intercept the callback from twitter and get some information (oauth verifier).
  • With the verifier we can get retrieve the access token (token and tokensecret) from twitter and store them (in Shared Preferences) to be able to sign further requests to the API.
As usual, each task that means download information from the Internet shouldn't be executed in the UI thread. To do this we can use AsyncTasks.

Well, knowing the "simple" OAuth flow and with the application created on the twitter developers site we can start to write.

The main activity will show a TextView to see if the user is authenticated or not, a button to erase the credentials, a button to send a tweet, a EditText to write the text to send and a TextView to see the result of the send (OK or KO). The send button and the edit text should are invisible if the user is not authenticated. The layout looks like this when the user is authenticated.



The main activity will check the Shared Preferences to see if we already have the tokens in onResume(). If we don't have tokens yet, we will launch an Activity to start the authorization flow. If we have tokens we have to put these tokens in our OAuthConsumer object and we can sign requests (later we'll see how).

private void checkTokens() {
  String token = shPref.getString(TwitterOauthKeys.TOKEN_KEY, "isnull");
   String tokensecret = shPref.getString(TwitterOauthKeys.TOKEN_SECRET_KEY, "isnull");
   
   if(token.equals("isnull")){
    text.setText("User NOT Authenticated");
    //disable buttonsend and company
    buttonsend.setVisibility(View.GONE);
    texttweet.setVisibility(View.GONE);
    //launch activity NewTwitterClientActivity to get tokens
    try {
    Intent NewClientIntent = new Intent(this, Class.forName("com.vidaltech.rest.oauth.NewTwitterClientActivity"));
    startActivity(NewClientIntent);
   } catch (ClassNotFoundException e) {
    Log.e(TAG,"Activity class not found");
   }
   }
   else{
    text.setText("User Authenticated!");
    //enable button send and edittext
    buttonsend.setVisibility(View.VISIBLE);
    texttweet.setVisibility(View.VISIBLE);
    
    //put tokens in OauthConsumer to sign requests
    consumer.setTokenWithSecret(token, tokensecret);
    
    
   }
   
 }


In the New Twitter Client Activity we need to create the OAuth objects and launch the task to retrieve the request token passing the objects to the task, as we've seen on the flow.

/*
    * Start a task to retrieve request token and launch browser/webview
    */
   new RetrieveRequestTokenTask(this, oauthConsumer, oauthProvider).execute();

This task simply retrieve the URL using the signpost library and launch a browser. Here we can see the doInBackground method of the task

protected Void doInBackground(Void... params) {
  try{
   /*
    * Getting the URL to redirect the user to authorize the application (request Token URL)
    */
  String url = oauthProvider.retrieveRequestToken(oauthConsumer, TwitterOauthKeys.CALLBACK_URL);
  Log.d(TAG,"Retrieved request token url "+url);
  /*
   * Redirecting user:
   * -creating a intent to launch browser
   * We use operator or to set various flags in the same int
   * flag no history to mark the new activity to be finished inmediately after user ends
   * flag from background to indicate that is launched from a background task, not from the interaction with user
   */
  Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_FROM_BACKGROUND);
  context.startActivity(browserIntent);
  
  }
  catch(Exception e){
   Log.e(TAG, "Error retrieving request token.");
   
  }
  return null;
 }

Now the browser should open and show the page to authenticate (if the user is not already logged in) and the page to authorize.

There is another possibility for doing this. We can use a WebView to show the Authentication/Authorization page in our application without launching the browser. At the end of the post you can see the details.

After that, we have to intercept the callback in our application. To do this we need to create a intent-filter. Each filter describes a capability of the component, a set of intents that the component is willing to receive. Check the android developer page to get more information about this.

<activity android:configChanges="keyboard|keyboardHidden|orientation" android:name="NewTwitterClientActivity" android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data  android:scheme="vidaltech" android:host="twitter"/>
            </intent-filter>
        </activity>

In the data tag, scheme and host must be the same that the callback URL we used in our app. The one that we used to build the OAuthProvider object. For example in this case is "vidaltech://twitter".

Now, following the flow, we can override the method onNewIntent(Intent intent) in that activity to get the URI and use it to retrieve the access token (in a separate task).

@Override
 protected void onNewIntent(Intent intent) {
  super.onNewIntent(intent);
  Log.d(TAGLIFE, "NEWCLIENTACTIVITY ONNEWINTENT");
  Log.d(TAG, "enter onNewIntent");
  final Uri uri = intent.getData();
  Log.d(TAG, "enter onNewIntent"+uri.getScheme());
  if(uri != null && TwitterOauthKeys.CALLBACK_URL.startsWith(uri.getScheme())){
   /*
    * launch the task to retrieve access token
    */
   new RetrieveAccesTokenTask(this,oauthConsumer, oauthProvider, shPref).execute(uri);
  }
  finish();
  
 }

In the retrieve acces token task we have to retrieve the tokens and store them in Shared Preferences.

@Override
 protected Void doInBackground(Uri... params) {
  Uri uri = params[0];
  //extract the oauth verifier from Uri using the static strings from OAuth class
  String oauthVerifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
  //launch the method to retrieve access token
  try {
   oauthProvider.retrieveAccessToken(oauthConsumer, oauthVerifier);
   /*
    * now we should have the token and tokensecret in oauthConsumer
    * Let's store them in shared preferences: we should use a Editor to write in sharedpreferences
    * with putString and then commit
    */
   Editor shPrefEditor = shPref.edit();
   shPrefEditor.putString(TwitterOauthKeys.TOKEN_KEY, oauthConsumer.getToken());
   shPrefEditor.putString(TwitterOauthKeys.TOKEN_SECRET_KEY, oauthConsumer.getTokenSecret());
   shPrefEditor.commit();
   
  } catch (OAuthMessageSignerException e) {
   Log.e(TAG, "Signer execption");
  } catch (OAuthNotAuthorizedException e) {
   Log.e(TAG, "Not autorized");
  } catch (OAuthExpectationFailedException e) {
   Log.e(TAG, "Expectation failed");
  } catch (OAuthCommunicationException e) {
   Log.e(TAG, "Communication error");
  }
  return null;
 }

In this moment we have our application authorized. Now let's see how to sign a request. To do this let's tweet somewhat. First we can check the twitter developers page to see which request we need to do. To tweet we have to do this POST request: POST statuses/update. 
Following the link we can see that the URL is http://api.twitter.com/1/statuses/update.format and we only have one required parameter: "status". This parameter is the message we want to send URL encoded.

So, do you remember that we have a send button and a edit text field in our layout? Let's use them. When the authenticated user push the button, we have to launch a task to make the REST request. And we already know how to do this with a GET method. Now we are going to use POST method, but is quite similar. Ah! And remember to sign the request before execute it!

First we have to create the HttpPost object with the URL. Then we have to add the data (message) using a NameValuePair object (from Apache library). Then sign the request (using our OAuthConsumer object) and that's all.

this.hPost = new HttpPost(uri);
  
  //put the data in a namevaluepair (from apache library)
  List dataToAdd = new ArrayList(1);
  
  dataToAdd.add(new BasicNameValuePair("status",data));
  
 
   hPost.setEntity(new UrlEncodedFormEntity(dataToAdd));
   
   /*
    * Before send we have to SING the request
    */
   
    consumer.sign(hPost);
   
   
   HttpResponse response = this.httpClient.execute(hPost);
   
   
   StatusLine statusLine = response.getStatusLine();
   if (statusLine.getStatusCode() == 200){
    Log.d(TAG, "post return OK");
    return true;
   }
   else{
    Log.d(TAG, "post return KO");
    return false;
   }

Take care with the Exceptions! Of course that is executed in an asynchronous task and at the end it will update the UI showing wether the tweet has been sent or not.


USING A WEBVIEW INSTEAD OF LAUNCHING A BROWSER

A WebView is a widget that allows to load web pages in our application.
Using a webview our app will be more clean and we will have more control on the authorization process.

So let's go.

First of all we don't need anymore the intent-filter and the method onNewIntent of the New Client Activity. In fact we don't need that activity, cos now we are going to intercept the callback using a WebViewClient.

A WebViewClient allows to control the events happening in a WebView. So we are going to use this to know when is loading a page. We will check if the page loaded is our CALLBACK URL and then we get the params from the URL to pass them to the RetrieveAccesTask.

So now, the RetrieveRequestTask won't launch a browser. Now will launch an activity which has a WebView in the layout. And we are going to pas the url to load using the method puExtra if the Intent.

Intent webViewIntent = new Intent(this.context, Class.forName("com.vidaltech.oauth.webview.WebViewActivity"));
  webViewIntent.putExtra("url", url);
  context.startActivity(webViewIntent);
  

We get the url from the intent.
Intent intent = getIntent();
        url = intent.getStringExtra("url");

This activity will have a WebView object and we will set a ClientWebView to be able to call to onPageStarted method. In this method we will get the url and call the Retrieve Acces Task
 webView = (WebView) findViewById(R.id.webview);
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        //webSettings.setBuiltInZoomControls(false);
        
        
        webView.setWebViewClient(new WebViewClient(){//Web client allow us to get control of the events

         /*
          * We have to use onPageStarted. Is called once.
          * onResource
          */

   @Override
   public void onPageStarted(WebView view, String url, Bitmap favicon) {
    Log.d(TAG," on pagestarted url "+url);
    
    if(url != null && url.startsWith(TwitterOauthKeys.CALLBACK_URL)){

     /*
      * here we have in the url with oauth_verifier
      * We just need to encode and use the task already created
      */

     Log.d(TAG," on pagestarted into if");
     
     Uri uri = Uri.parse(url);
     new RetrieveAccesTokenTask(context,TwitterOauthKeys.oauthConsumer, TwitterOauthKeys.oauthProvider, shPref).execute(uri);
     
     callFinish();//finish the activity
    }
    else{
      //Check if we already have the tokens cos always is called onPageStarted
      String token = shPref.getString(TwitterOauthKeys.TOKEN_KEY, "isnull");
     
      //if we have the tokens we can close this activity and go back to the main activity
      if(!token.equals("isnull")){
       callFinish();//finish the activity
      }
     }
    
   }
         
        });
        
        webView.loadUrl(url);
 
As we can see, we need to check if we have the tokens in this activity (also in the main activity) in the method onPageStarted. If we have the tokens we can finish the activity to go back to the main activity and send tweets.

We have also to store the OAuthObjects in another class to be able to acces them from the differents activities.


No comments:

Post a Comment