Sunday, April 7, 2013

OAuth-2 for Social Media in Java

Overview
This post describes some basic implementation of OAuth-2 for applications known as 3-legged OAuth-2 protocol. We use Facebook and Twitter API to illustrate the basic use case for OAuth-2.  We assume the reader is familiar with the basic of Authentication (Basic, OAuth-2) and knowledgeable in Java programming.

In a 3-legged OAuth-2 protocol, users authorize an application to access the provider of services or resources. The consumer application exercises the API of the service provider on the behalf of the user by using a consumer key and secret granted by the provider.


The protocol for the application to access the protected services and resources is
  1. Application developer requests privilege to access protected resources on the behalf of the user
  2. The provider grant access to the application by supplying a consumer key and secret
  3. The application developer retrieve the authentication token using the consumer key and secret
  4. The provider generates the authentication token
  5. The application retrieves the session token (key and secret) using the authentication token
  6. The session token is used as argument to any request to the provider API (i.e. REST GET, POST,..)



Facebook
As expected access to the Facebook API relies on the consumer key, API_KEY (line 4) and secret key API_SECRET (line 5) to generate the authentication token.
The URL for the Facebook end-point (line 2) is defined as a constant. A well-thought implementation would defines these constants in a configuration file as there are no guarantee that Facebook will not change the URL end-point in the future.

The purpose of the constructor (line 11 - 14) is to instantiate the JSON rest client with or without the session key whenever available.
Any security mechanism relies on
  • Authentication: authenticate (line 19)
  • Authorization: authorize (line 20)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class FacebookAuth {
  final static String OAUTH_URL =
           "http://www.facebook.com/login.php?api_key=";
  final static String API_KEY = "apikey";
  protected final static String API_SECRET = "apisecret";
  static String apiKeyValue = getKeyValue();
  static String apiSecretValue = getSecretValue();
  
  JsonRestClient _client = null;
 
  public FacebookAuth(final String sessionKey) {
    _client = (sessionKey == null) ?
    new JsonRestClient(apiKeyValue, apiSecretValue) :
    new JsonRestClient(apiKeyValue, apiSecretValue, sessionKey); 
  }
  
    // Authenticate the client as a desktop application client 
    // using the token generated from the consumer key and secret.
  public String authenticate() { ... }
  public String[] authorize(final String authToken) { .. }
}
  

The JSON REST authentication consists of creating an authentication token for the session and generate the authenticated end-point url to process the request.
The helper methods and validations of arguments of methods have been removed for the sake of clarity

public String authenticate() {
  String url = null;
  boolean isDesktop = _client.isDesktop();
   
  try {
    String token = _client.auth_createToken();
    url = new StringBuilder(OAUTH_URL)
         .append(_client.getApiKey())
         .append("&v=1.0&auth_token=")
         .append(token)
         .append("&req_perms=status_update, read_stream,publish_stream") 
         .toString();
  }
  catch( FacebookException e) { ... }
  return url;
}

The authorization step authorize uses the authentication token authToken to retrieve the key of the current session sessionKey. The secret key for the session sessionSecret is retrieved by the JSON client, through the invocation of the method getSecret.

public String[] authorize(final String authToken) {
  String[] results = null;
   
  try {
    String sessionKey = _client.auth_getSession(authToken,true );
    String sessionSecret = _client.getSecret();
    results = new String[] { sessionKey, sessionSecret };
  }
  catch( FacebookException e) { ...}
  return results;
}

Twitter
The Twitter OAuth-2 API is very similar to the Facebook Authentication API. The client application maintains the handle to the authentication services as defined by its consumer key and secret, which are stored by the application. As we cannot assume that there is only one request tokens supplied by the service provider, those tokens are cached as key value pair in requestTokenCache (line 5).

The constructor retrieves the consumer key consumerKey (line 10) and the consumer secrete consumerSecret (line 11).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class TwitterAuth  {
  public static final String OAUTH_URL =
       "http://www.xyztest.com:3000/dashboard/signup_oauth?";
  private static Map<String, String> twitterOath = null;
  private static Map<String, RequestToken> requestTokenCache 
                          = new HashMap<String, RequestToken>();
  private Twitter _twitter = null;
 
  public CTwitterAuth() {
    String consumerKey = twitterOath.get("oauth.consumerKey");
    String consumerSecret = twitterOath.get("oauth.consumerSecret");
           
    _twitter = new TwitterFactory()
         .getOAuthAuthorizedInstance(consumerKey, consumerSecret);
  }

  public String authenticate(final String user) 
  public String[] authorize(
                   final String user, 
                   final String auth_token
  ) 
}

The implementation of the authentication through the method authenticate extracts the requestToken. If it exits, the request token is used to retrieve the URL for the Twitter authorization end point twitterAuthURL.
Finally, the request token associated to this specific user is cached.

 public String authenticate(final String user) {
   String twitterAuthURL = null;
     
   try {
     RequestToken requestToken =
                 _twitter.getOAuthRequestToken(OAUTH_URL);

     if( requestToken != null) {
       twitterAuthURL = requestToken.getAuthorizationURL();
       requestTokenCache.put(user, requestToken);
     }
  }
  catch( TwitterException e) {  .... }
  return twitterAuthURL;
}

The authorization accesses the request token associated to this user and retrieve the access token accessToken. The access token is very similar to the session token available in the Facebook API and contains the token and its secret, and wrapped into the array tokenStr

public String[] authorize(
   final String user, 
   final String auth_token) {
  
  String[] tokenStr = null;
     
  try {
    RequestToken requestToken  = requestTokenCache.get(user);
    if( requestToken != null) {
       AccessToken accessToken =
         _twitter.getOAuthAccessToken(requestToken);
          
       tokenStr = new String[]  { 
          accessToken.getToken(),                             
          accessToken.getTokenSecret()
       };
     
       if( requestTokenCache.remove(user) == null ) {
        logger.error("can't remove user from auth. cache");
       }
    }
  }
  catch(TwitterException e) {  ... }
  return tokenStr;
}


Note:
Although the OAUTH-2 standard is well-defined, the social network providers update their API regularly. Versioning of REST API (resource, actions..) remains challenging so there is no guarantee that new API introduced by the provider is backward compatible with the existing client code. Therefore there is no guarantee that the authentication code for Facebook and Twitter REST API is still valid at the time you read this post. However, the concept and generic implementation of the OAUTH-2 protocol, described in the post should be easily portable to any new API or other social network provider.

A final note: This implementation lists the authentication URL and REST end-point available at the time of this post. These parameters are very likely subjected to changes by Facebook and Twitter.


References
OAuth-2 Specification 
github.com/prnicolas

No comments:

Post a Comment