Wednesday, January 14, 2009

Simple OAuth Client and Server Examples

The OAuth Google code (http://code.google.com/p/oauth/) is woefully undocumented, so I put together below a zeroeth order approximation to how things actually work. To use this, you need both the Open Social Java implementation (for the client) and the OAuth Java implementation. Check them both out from the Google Code SVNs as described in earlier posts.

In the examples below, we will use OAuth's two-legged authentication. This is the server-to-server authentication used by OpenSocial gadgets. It is appropriate when no human intervention is needed (or is possible) in the authentication process. Also, we are going to just look at shared secret key-style authentication, using the HMAC-SH1 algorithm and symmetric key encryption. The OAuth code will also support public/private asymmetric keys.

Finally, note that the steps for generating the OAuth consumer key and consumer secret are out of scope. We will just use the key and secret from the earlier OpenSocial gadget experiments. Generally, shared secrets will need to be communicated through a separate registration process. Presumably, the server would use the consumer key sent by the client to look up the secret key on the server side from a database, but check the OAuth specification for details.

Server Code
We'll implement a dummy server as a JSP. You can put this in an Apache Tomcat server under a webapp called OAuthTest. You will also need to put several jars in webapp/OAuthTest/WEB-INF/lib:

ls apache-tomcat-5.5.27/webapps/OAuthTest/WEB-INF/lib/
commons-codec-1.3.jar jetty-6.1.11.jar
commons-httpclient-3.1.jar jetty-util-6.1.11.jar
commons-logging-1.1.jar junit.jar
httpclient-4.0-beta1.jar oauth-core-20090108.jar
httpcore-4.0-beta2.jar

The above jars are all from OAuth/java/lib, plus the oauth-core jar that you generated when you compiled things (see previous post--use "mvn clean install").

Here at last is the actual JSP for the dummy service. I call this OAuthTest.jsp.

--------------------

<%
//Presumably this should actually be looked up for a given key.  Use a real key.
String consumerSecret="123121212yourkeyhere18918";

//Presumably the key is sent by the client. This is part of the URL, after all.  Use a real key.
String consumerKey="orkut.com:1231212122";

//Construct the message object. Use null for the URL and let the code construct it.
OAuthMessage message=OAuthServlet.getMessage(request,null);

//Construct an accessor and a consumer
OAuthConsumer consumer=new OAuthConsumer(null, consumerKey, consumerSecret, null);
OAuthAccessor accessor=new OAuthAccessor(consumer);

//Now validate. Weirdly, validator has a void return type. It throws exceptions
//if there are problems.
SimpleOAuthValidator validator=new SimpleOAuthValidator();
validator.validateMessage(message,accessor);

//Now what? Generate some JSON here for example.
System.out.println("It must have worked"); %>


-------------------

Client Code

To build an OAuth client, we can start from our earlier OpenSocial client. For convenience, we will leave in the OpenSocialUrl and OpenSocialHttpRequest classes, which help construct and execute the REST invocation, but one could easily eliminate this and use the standard java.net classes that underly these two.

Here's the client code. Save it in a file called MyOAuthClient.java in OpenSocial's java/samples directory.
---------------------
import org.opensocial.data.*;
import org.opensocial.client.*;
import net.oauth.*;
import java.util.*;

public class MyOAuthClient {

public static void main(String[] args) {
MyOAuthClient mosc=new MyOAuthClient();
}

public MyOAuthClient() {
String REST_BASE_URI=
"http://localhost:8080/OAuthTest/OAuthTest.jsp";
String CONSUMER_SECRET=
"uynAeXiWTisflWX99KU1D2q5";
String CONSUMER_KEY=
"orkut.com:623061448914";
// String VIEWER_ID=
// "03067092798963641994";
String VIEWER_ID="08354253340777199997";

try {
OpenSocialUrl requestUrl = new OpenSocialUrl(REST_BASE_URI);
OpenSocialHttpRequest request=new OpenSocialHttpRequest(requestUrl);
requestUrl.addQueryStringParameter("xoauth_requestor_id", VIEWER_ID);
requestUrl.addQueryStringParameter("st", "");

String requestMethod=request.getMethod();
String postBody = request.getPostBody();

OAuthMessage message =
new OAuthMessage(requestMethod, requestUrl.toString(), null);

OAuthConsumer consumer =
new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, null);
consumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.HMAC_SHA1);

OAuthAccessor accessor = new OAuthAccessor(consumer);
accessor.accessToken = "";
message.addRequiredParameters(accessor);

for (Map.Entry p : message.getParameters()) {
if (!p.getKey().equals(postBody)) {
requestUrl.addQueryStringParameter(
OAuth.percentEncode(p.getKey()),
OAuth.percentEncode(p.getValue()));
}
}

//Take a look at the signed URL
System.out.println("Signed REST URL: "+requestUrl.toString());

//Done with signing. Now back to OpenSocialBatch's submitRest()
//Finally, get the response. This is the meat of the getHttpResponse
//method, without the error checking.
request.execute();
}

catch(Exception ex) {
ex.printStackTrace();
}
}
}
------------------
Compile this from the OpenSocial SVN checkout's java directory with the command ant compile-samples.

Now set your classpath and execute. The following UNIX commands will do the trick:
export CP=`echo $HOME/opensocial-java-client-read-only/java/lib/*.jar | tr ' ' ':'`

export CP=`echo $HOME/opensocial-java-client-read-only/java/dist/*.jar | tr ' ' ':'`:$CP


java -classpath $CP:/Users/marlonpierce/opensocial-java-client-read-only/java/samples/bin/ MyOAuthClient


Check the output of your Tomcat server's catalina.out to see if it worked.

Obviously this has all been a simple exercise to see how to get things going and needs a lot of work to make it a real service.


3 comments:

deardooley said...

so I've put up a profile service to query TeraGrid user and allocation information. right now it outputs user profiles and allocations in xml and json formats. the structure is still up for grabs as it's relatively easy to modify. would it be helpful to output using the opensocial profile structure, or perhaps the MS user profile structure. I'm thinking of the easiest way for consumers to interop with both each other and the TG.

Unknown said...

how did you figure out your VIEWER_ID?

Ismael Juma said...

Hi,

Regarding the exceptions thrown during validation in the server code, there's a handy method called handleException in OAuthServlet. You pass the exception and the response and it will send the right status code back (assuming it's a servlet, of course).

Best,
Ismael