Pages

Thursday, September 27, 2012

An OAuth Provider for Java based on Scribe

If you've been following my travels with ABBI, you know I recently embarked on developing a sample OAuth provider service.  A day later, and I've managed to implement it.  There's one bug with the URL decoding that I need to work on (basically, what starts out as a + winds up as a space later on).  This is intermittent, but not sufficiently so that I was unable to find it.  It shows up when dealing with tokens, so the quick fix is to swap out some characters when I create them.  The longer fix will be to figure out where the double decode is occurring.

I started with a Java OAuth client library known as Scribe, which has a lot going for it.   The solution includes the Scribe library, and 9 additional classes.  All told, it's less than 650 lines of code, and relatively sparse on documentation.  And I managed to get it to work in two days. Two days.  I'm done listening to complaints about OAuth complexity.

So, given my lack of copious documentation, here's an explanation of how this stuff works.

InvalidOAuthRequestException

public class InvalidOAuthRequestException extends Exception {
   public InvalidOAuthRequestException(String reason) {
   super(reason);
  }
}

This is an exception class that I use to report a variety of different reasons why an OAuth request might not be valid.  As far as my code is concerned, I don't need to differentiate these.  If someone sent the wrong data, the error message is enough to tell you why my provider rejected the request.  Classes don't get much shorter than this.

OAuthProvider 

public interface OAuthProvider {
  public Token getRequestToken(HttpServletRequest req) 
    throws InvalidOAuthRequestException;

public String authorizeUser(HttpServletRequest req) throws     
    throws InvalidOAuthRequestException;

public String getCallback(HttpServletRequest req) throws 
    throws InvalidOAuthRequestException;

public Token getAccessToken(HttpServletRequest req)
    throws InvalidOAuthRequestException;

public void validate(HttpServletRequest req)
    throws InvalidOAuthRequestException;

   public TokenRepository getRepository();
}

This interface describes a provider service, and contains six key methods.  In the OAuth workflow, there are four main steps:
  1. The first step is to get a request token.  The getRequestToken() method of OAuthProvider handles most of the effort.  It takes a HttpServletRequest that has been generated by calling the request token end point appropriately, and generates a new token. 
  2. The next step is dealing with the login flow.  Once it has a request token, the client needs to pass it the "login" page of the service provider.  The service provider can either accept or reject the users credentials, and the user can agree or cancel on the authorization.  
    1. If the user credentials are accepted AND the user authorizes the calling application to access their data, then a temporary token and verification string is created and the user is redirected to the client application with those values.  The temporary token is created using the authorizeUser() method.  That method returns the callback string which can be used to redirect the user appropriately.
    2. If the user credentials are not accepted, OR the user does not authorize the calling application, we need to return back to the calling application WITHOUT passing a temporary token.  That's what getCallback() helps with.  It generates the appropriate callback string to handle this "unauthorized" path.
  3. Having been authorized (following from 2.1 above), the client application now must request an access token.  The getAccessToken() method of OAuthProvider again, handles most of that effort.
  4. Finally, API requests need to verify that they are being accessed with a valid access token.  That's what the validate() method does.
  5. Tokens, Tokens, Tokens.  Every provider deals with a ton of tokens.  It has to be configured with a place to create and store these things persistently.  getRepository() provides access to the provider's token repository.

OAuthProvider10aImpl

There are number of variations on a theme that could be used to implement an OAuth provider.  I picked a few reasonable settings and implemented them.  It uses the HMAC-SHA1 signature method as the only method that it expects clients to use. I don't need the complexity of RSA-based signing, but I don't want to support PLAINTEXT signing.  

The core methods of my implementation follow the same general pattern:

  • Map the ServletRequest into an OAuthRequest.
  • Validate the signature of the OAuthRequest.
  • Perform some business logic with various OAuth parameters, manipulating tokens as necessary.
  • Return the token (or other data) needed for the next step.

TokenRepository

import org.scribe.model.Token;

public interface TokenRepository {
  enum TokenType {
    CLIENT_KEY,
    REQUEST_TOKEN,
    TEMP_TOKEN,
    ACCESS_TOKEN
  };
  public Token get(TokenType type, String key);
  public void put(TokenType type, Token t);
  public Token remove(TokenType type, String key);
  public Token create(String data);
  public boolean isNonceUsed(String nonce);
  public void saveNonce(String nonce);
}

The TokenRepository interface defines an interface to a persistent store of tokens, and provides a mechanism to create them.  It also keeps track of "nonce" values, single use values that ensure that replay attacks cannot be used in the protocol.  The get/put/remove methods work like the related Map methods.  In fact, my implementation of a memory based token repository uses just that as the backing store.  The isNonceUsed and saveNonce methods are interfaces to something like a Set.  The saveNonce() method adds the nonce to the set of previously seen nonce values.  The isNonceUsed method checks to see if a nonce has been seen before.  In the real world, you don't save every nonce forever.  Typically, you save those that have been used in the last 5-10 minutes, and refuse to process any request that has a time stamp that is older than that.

MemoryTokenRepositoryImpl

MemoryTokenRepositoryImpl is an implementation of the TokenRepository class.  It uses a HashMap to keep track of tokens and nonces.  The get/put/remove methods map into get/put/remove on  its hashmap with the TokenType prefixed to the key.  The same hashmap is also used to store nonce values, with "nonce:" prepended to the nonce value serving as the key.  It's a totally simple and useless implementation, unless of course you need something quick and dirty for testing.  In which case, it works just fine.

Tokens (and secrets) are created by generating about 24 bytes worth of random bits (using java.util.Random), and then base64 encoding the results, and splitting the string into two parts.  The first part becomes the key, and the second part the secret.

Util

This was really intended to be a utility class, but instead it is what does most of the heavy lifting.

public static OAuthRequest createRequest(HttpServletRequest req)

This method creates an OAuthRequet object (a class existing in Scribe) from an existing HttpServletRequest object.  OAuthRequest is the class that Scribe uses to generate signatures from, which is why I do this.  I don't want to have to regenerate the signature creation logic.  This method is called very early by each of the request processing methods found in OAuthProvider10aImpl.

public static Map<String,String> parseHeader(String header)

This method is used to parse the "Authorization" header of a request.  OAuth parameters can appear either in query parameters, as POST parameters, or in the Authorization header.  The parser is dead simple.  It splits content at sequences of commas and spaces, and then splits those into a name and value part.  Because of the very smart way that OAuth defined the OAuth schema for this header parameter, you can use a dump parser like this.  This method is called by createRequest above to read the Authorization header of a request and store the parameters into the OAuthRequest.

public static Verb getVerb(String method) 

This method maps a string into an appropriate enumeration type, turning "GET" into Verb.GET, and "POST" into Verb.POST.

public static Token validate(OAuthRequest r, TokenRepository tokens) throws InvalidOAuthRequestException

This method does the real work .  It checks the OAuth Version (1.0 only), ensures the request is not too old, verifies the nonce hasn't been used before, maps the consumer_key and token to a consumer_secret and token_secret, regenerates the signature and compares it to the existing one.  If, somewhere along the way, one of these tests fails, it throws an InvalidOAuthRequestException explaining the stage at which the failure occured.

public static String getSignature(OAuthRequest r, String consumerSecret, String tokenSecret)

This method computes the signature from the request, given the consumer and token secrets.  This is five lines of code.  It simply puts together the right parts from elsewhere in Scribe to generate the signature.

OAuthRequest

This is a servlet that implements the token request and access request endpoints.  It expects something implementing the OAuthProvider interface I described above to be stored in "OAuth.Provider" attribute of the application.  The doGet() method usually returns a 404 (but if the URL includes the word test, it duplicates doPut()).  The doPost() method gets the operation to perform from the end of the URL Path (either /request or /access).  It calls getRequestToken() or getAccessToken() appropriately from the OAuthProvider.  Then it writes the returned token to the response in application/x-www-form-urlencoded format.

init.jsp

The OAuthRequest servlet needs a provider.  This JSP creates one and stuffs it into the appropriate application attribute.

Authorize.jsp

This is my login page.  Right now, it looks something like this:

By entering your name and password below and clicking on "I Agree", you agree to the following:
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Username: 
Password: 


The point demonstrated is that Authorization can be worded as "consent".

validateCredentials.jsp

 The whole point of OAuth is to validate that requests have been authorized.  So this page is a dummied up API call.  It looks something like this right now:


<%  response.setHeader("Content-Type", "application/json"); 
    OAuthProvider provider =
      (OAuthProvider)application.getAttribute("OAuth.Provider");
    try
    { provider.validate(request); }
    catch (InvalidOAuthRequestException ex)
    { ex.printStackTrace();
      response.sendError(401);
    }
%>{ 
 "Test": "This is a test",
 "name": "Hi Keith",
 "screen_name": "Did this work?"
}

It fails if you aren't sending an OAuth signed request, and succeeds if you are.  The content is just some JSON that my ABBI application knows how to process.

Download

Everything you need to build this OAuth provider is available for download on Google code.  Just download the ABBI-Auth project from svn here

No comments:

Post a Comment