Home > OAuth > Improved Twitter OAuth for Android

Improved Twitter OAuth for Android

A couple of months ago, I published a post entitled A 30 minute guide to integrating Twitter in your Android application.. The post presented a sample Android application to integrate Twitter. Using the signpost library, the user was able to authorize our application to send tweets on his/her behalf. It seems that everyone is migrating to Oauth 2.0, but Twitter is still stuck at OAuth 1.0. Nevertheless, I still wanted to update the sample we did a couple of months ago for 3 reasons : 

I’ll start by introducing the application (for those who read the previous article, it should look pretty familiar.

The sample app UI

Granted, the user interface looks pretty basic, but it gets the job done. We’ve got

  • a Launch OAuth flow button that will take the user through the OAuth flow.
  • a Clear credentials button, that will remove any authentication tokens stored in the app.
  • a Label, showing the status of a Twitter API call.

Unauthorized API access

When we start the application for the first itme, you’ll see the following error message :

x

What you are seeing is the outcome of the Twitter API call. Obviously, in order to perform a Tweet, the user needs to authorize our application to send tweets on his behalf. Failure to do so results in an Authentication error.

The API call is executed when this main activity is launched.

In order to succesfully send a tweet, the user needs to authorize the request.

Authorizing the application

When clicking on the Launch OAuth Flow button, we’ll start the OAuth web based flow. In the previous article, we used Signpost to handle the Oauth flow, and had a pretty complex flow, where we started an activity, launched the browser, started background tasks, and had to deal with onNewIntent calllbacks during the OAuth flow.

On top of that, launching the browser to show the Twitter authorization pages, a scenario where all of a sudden our application lost control and moved control to the browser, is a pretty unnatural flow with a lot of negative side-effects for the user. To name a couple :

  • The user find himself in a browser environment all of a sudden.
  • It’s clear he’s no longer in our application.
  • What if the users decides to go back, or exit the browser app ?
  • What if the user was browsing some site in his browser, will that page all of a sudden get replaced by our Twitter authorization page ?

In the previous sample, we launched a browser like this :

final String url = provider.retrieveRequestToken(consumer, Constants.OAUTH_CALLBACK_URL);
Log.i(TAG, "Popping a browser with the authorize URL : " + url);
Intent intent = 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(intent);

 

the Android WebView component

In this sample, we’re not going to pop a full browser, but we’re going to start a regular Android activity, and use the WebView component to launch our authorization page

@Override
protected void onResume() {
	super.onResume();
	WebView webview = new WebView(this);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.setVisibility(View.VISIBLE);
        setContentView(webview);

        final OAuthHmacSigner signer = new OAuthHmacSigner();
        signer.clientSharedSecret = Constants.CONSUMER_SECRET;

	OAuthGetTemporaryToken temporaryToken = new OAuthGetTemporaryToken(Constants.REQUEST_URL);
	temporaryToken.transport = new ApacheHttpTransport();
	temporaryToken.signer = signer;
	temporaryToken.consumerKey = Constants.CONSUMER_KEY;
	temporaryToken.callback = Constants.OAUTH_CALLBACK_URL;

	OAuthCredentialsResponse tempCredentials = temporaryToken.execute();
	signer.tokenSharedSecret = tempCredentials.tokenSecret;

	OAuthAuthorizeTemporaryTokenUrl authorizeUrl = new OAuthAuthorizeTemporaryTokenUrl(Constants.AUTHORIZE_URL);
	authorizeUrl.temporaryToken = tempCredentials.token;
	String authorizationUrl = authorizeUrl.build();

	webview.loadUrl(authorizationUrl);
}

As you can see, a setup a WebView component (taking up the entire activity space), enable javascript (most authorization pages require javascript), and load up the OAuth 1.0 authorization URL in the WebView component.

Before we can start building the authorization URL, we first need to setup our OAuth signer object, request a temporary token, and use that temporary token to put in our authorization URL.

Twitter OAuth constants

All Twitter specific Oauth parameters are defined in a constants file.

	public static final String CONSUMER_KEY = "PUT YOUR TWITTER OAUTH CONSUMER KEY HERE";
	public static final String CONSUMER_SECRET= "PUT YOUR TWITTER OAUTH CONSUMER SECRET HERE";

	public static final String REQUEST_URL = "http://api.twitter.com/oauth/request_token";
	public static final String ACCESS_URL = "http://api.twitter.com/oauth/access_token";
	public static final String AUTHORIZE_URL = "http://api.twitter.com/oauth/authorize";

	public static final String	OAUTH_CALLBACK_URL		= "http://localhost";

What we need are

  • the OAuth consumer key and secret (can be found on the Twitter dev page for your app)
  • the 3 OAuth endpoints (request,authorize and access)
  • a callback URL.

When the authorization URL is launched in the Webview, you should see the following screen (when already logged into Twitter). When not logged in, a page will be shown allowing you to enter your Twitter credentials before moving to this page. Included on the page is some information related to your application (as you’ve defined them on the Twitter dev page), and an authorize button.


x

When the user hits the authorize button, Twitter will perform a redirect using our callback URL (set to http://localhost)

x

Intercepting the callback

And here’s where the magic happens. As Twitter redirects to the callback URL, it’s up to our app to intercept this redirect, pick up the 2 OAuth tokens from that callback URL (oauth token and oauth verifier). This authorization (and verifier) token is what we need in order to get an actual OAuth access token that is required to perform the Twitter API call.

Luckily for us, the WebView component allows us to register a WebViewClient, that has an important hook that we’ll implement, the onPageStarted method.

This method is called each time a page is loaded into the WebView. As we’re particulary interested in our callback URI, we’ll only implement our logic when the URL in the WebView matches our Callback URL. When this is the case, we extract the oauth_token en oauth_verifier.

With the oauth_token en oauth_verifier, we can contact Twitter and exchange them for an actual OAuth access token.

As you can see from the code below, we extract both tokens from the callback URL.
We construct an OAuthGetAccessToken object, set the required properties (transport, temp authorization token, verifier token, signer and consumer key) and call it’s execute method to retrieve the actual access token. The access token is embedded in the OAuthCredentialsResponse object.

String requestToken  = extractParamFromUrl(url,"oauth_token");
String verifier= extractParamFromUrl(url,"oauth_verifier");

signer.clientSharedSecret = Constants.CONSUMER_SECRET;

OAuthGetAccessToken accessToken = new OAuthGetAccessToken(Constants.ACCESS_URL);
accessToken.transport = new ApacheHttpTransport();
accessToken.temporaryToken = requestToken;
accessToken.signer = signer;
accessToken.consumerKey = Constants.CONSUMER_KEY;
accessToken.verifier = verifier;

OAuthCredentialsResponse credentials = accessToken.execute();

What we need to do now is store the OAuth access token (and secret) in the shared preferences of our app. I’ve created a simple SharedPreferencesCredentialStore that is capable of reading / writing the 2 tokens from / to the shared preferences.

CredentialStore credentialStore = new SharedPreferencesCredentialStore(prefs);
credentialStore.write(new String[] {credentials.token,credentials.tokenSecret});

 

Obviously, we don’t have anythiung running on localhost, so in order to avoid the user getting confronted with an HTTP 404 Not Found error in his WebView, we hide the WebView (by setting the visibility to gone).

view.setVisibility(View.INVISIBLE);
startActivity(new Intent(OAuthAccessTokenActivity.this,AndroidTwitterGoogleApiJavaClientActivity.class));


Executing the API

Upon returning to the main activity, with our OAuth access tokens safely stored in the shared preferences, all that’s left to do know is execute the API call. This is done through the following utlity method.

public static void sendTweet(SharedPreferences prefs,String msg) throws Exception {
		String[] tokens = new SharedPreferencesCredentialStore(prefs).read();
		AccessToken a = new AccessToken(tokens[0],tokens[1]);
		Twitter twitter = new TwitterFactory().getInstance();
		twitter.setOAuthConsumer(Constants.CONSUMER_KEY, Constants.CONSUMER_SECRET);
		twitter.setOAuthAccessToken(a);
		twitter.updateStatus(msg);
}

We retrieve the tokens from the shared preferences, use them to construct a Twitter AccessToken (Twitter4J component), and use the Twitter object to send our Tweet. Our main activity will confirm that the tweet has been sent.

To double check, you can always check your Twitter page as well :

Conclusions

I tried to show you a couple of things in this article :

A simplified way of dealing with the Oauth flow
By using a WebView component, we can have more control over the Oauth flow, as it’s kept within the Android Activity lifecycle, without starting external programs. You can opt to run it fullscreen, or embedded in an Activity, providing some additional context to the user.

Using non-Google APIs in conjuction with the google api java client
So far, we’ve only used Google APIs when using the Google APIs Client Library for Java. Here we’ve shown that the OAuth flow was completely handled by the library, right untill the point where we retrieved the access token. After that, control was given to Twitter4J, a Java library for the Twitter API compatible with Android.

References

  1. kai
    September 2nd, 2011 at 10:31 | #1

    could you include the jar files in the source? I copied them from other places, but not sure they’re the same ones you used… When I try running your program after updating the key/secret, i get “Authentication error: Unable to respond to any of these challenges”

    any help appreciated!

  2. Felipe
    October 8th, 2011 at 17:34 | #2

    I don’t know how did you get implement the method “extractParamFromUrl(String url, String paramName)”. I tried everything, but i dont know how i can get the parameters, and if the parameters was sent by GET or POST. You can share your solution?
    Thankful.

  3. Manju
    November 8th, 2011 at 08:55 | #3

    Hi ,

    I tried to compile this project but i am getting error like

    Description Resource Path Location Type
    Unbound classpath variable: ‘M2_REPO/com/google/api/client/google-api-client-extensions-android2/1.4.1-beta/google-api-client-extensions-android2-1.4.1-beta.jar’ in project ‘twitter1′ twitter1 Build path Build Path Problem

    if anyone knows pls let me know

  4. Andy Sun
    November 10th, 2011 at 07:52 | #4

    @Author: Thank you so much for this tutorial. Everything’s much more clearer now. Can’t thank you enough since I’ve been stuck on getting this to work for a while now :S

    @Kai: Here’s the link to the jars used in this tutorial. Not all of them are needed tho: http://code.google.com/p/google-api-java-client/downloads/detail?name=google-api-java-client-1.4.1-beta.zip

  5. Andy Sun
    November 10th, 2011 at 08:37 | #5

    Hi I’m getting a blank screen when I click on the Launch OAuth Flow button, any idea what might be causing this?

  6. Andy Sun
    November 10th, 2011 at 10:16 | #6

    @Andy Sun
    I fixed my white screen problem. For those of you who has the same issue. You must have the same callback_URL on your twitter app as well as in your android app, even if it’s empty or some random url. It can be any random url since the app is going to intercept the callback anyway. hope this helps

  7. November 20th, 2011 at 23:47 | #7

    I get the following runtime error when trying to run the sample code (everything compiles fine):

    java.lang.NoClassDefFoundError: com.google.api.client.auth.oauth.OAuthGetTemporaryToken
    at com.ecs.sample.OAuthAccessTokenActivity.onResume(OAuthAccessTokenActivity.java:54)

    The only thing I did was download the google-api-java-client-1.6.0-beta (http://code.google.com/p/google-api-java-client/wiki/Setup) instead of the 1.4.1-beta that was referenced in the github source. I see that OAuthGetTemporaryToken is part of the google-oauth-client-1.6.0-beta.jar, so I added that external library reference to the project.

    The jar reference seemed to work because I was getting “java.lang.NoClassDefFoundError: com.google.api.client.auth.oauth.OAuthHmacSigner”, and both classes are in the same com.google.api.client.auth.oath package. Once I referenced the jar, the OAuthHmacSigner runtime error went away and the OAuthGetTemporaryToken started.

    The only other thing I can mention is that in the 1.6.0-beta version of the google apis I couldn’t find a replacement jar for the google-api-client-googleapis-1.4.1-beta.jar. The google URL mentioned above says that it is a Maven artifact, but it is not in the download zip file.

    Any ideas?

  8. Elvin
    December 15th, 2011 at 11:31 | #8

    the sample codes work great.. i really appreciated your owesome work on the sample.
    i just have one questions, is there anywhere we can just authorized once can post tweet without reauthorize until we clear the credentials..

  9. Bobby
    January 26th, 2012 at 03:22 | #9

    Hi, could you release the source code for this example please,

    i confuse about the jar that needed for this tutorial. because google api-client contain many jars

  10. Patrick
    February 4th, 2012 at 20:11 | #10

    This and the 30 minutes article are both great tutorials! Thanks for sharing it.

  11. Hitendrasinh
    March 16th, 2012 at 11:26 | #11

    @Jeff Jolley
    I faced same problem.There are some dependencies(jar files) that we need to use along with google-api-java-client-x.x.x-beta(i have used 1.4.1 beta).
    I have included these four jar files and it works fine.

    1. Apache HTTP Client version 4.0.3
    2. Google Guava version r09
    3. Jackson version 1.6.7
    4. Google GSON version 1.6

    cheers!!!

  12. Hitendrasinh
    March 16th, 2012 at 13:08 | #12

    How to logout from twitter?
    As the clear credentials don’t work.If i clear credentials still i can tweet.

    • admin
      March 25th, 2012 at 13:42 | #13

      Clearing the credentials (removing the Oauth tokens) from the shared preferences should be sufficient. The sample code uses twitter.setOAuthAccessToken, where the oauthAccessToken is constructed from whatever is in shared preferences. If those are deleted, updating your status msg should fail.

  13. ML
    March 16th, 2012 at 15:25 | #14

    @Jeff Jolley

    How did you resolve the 1.6.0 NoClassDefFoundError?

  14. JGR
  1. August 8th, 2011 at 20:51 | #1
  2. September 5th, 2011 at 22:37 | #2