Pages

Tuesday, October 23, 2012

OAuth Enabling a Web Application


While most of what I post is of interest to just a Healthcare crowd, this particular post is of general interest, because the code I've put together should work with any kind of web site.


One of the discussions in the ABBI pull workgroup is about how to secure access to the API.  We'd discussed using OAuth, but there are some concerns that it is too difficult to implement on an existing website.  Having developed both client and server side RFC 5849 implementations (based on OAuth 1.0a), I decided to see what it would actually take.  It turns out to be about 2000 lines of code and about a week of my time.  Having done so, it should only take an implementer using the tools I've produced a day.

To do this exercise, I needed to refactor my OAuth Provider implementation.  There are two parts of OAuth.  The first part is the Authorization Workflow, which involves a sequence of exchanges to obtain a credential used to access OAuth protected APIs.  The second part is Request Validation Process used to verify authorization credentials used to access various pages.  My original implementation was complicated because various parts of the Authorization Workflow also go through Request Validation Process.

What follows below is a description of the code I put together that allows you to OAuth enable a web site.  It is a fairly long post.  If you want to cut to the chase, I won't be offended.

On a side note, although everything done here is Java based, the same kind of thing could be done for other kinds of sites.  You should also be able to use the OAuthFilter and OAuthServlet in conjunction with IIS driven web sites that run inside of a Tomcat web server (or other JSP 2.3/Servlet 1.2 compliant container).

Authorization Workflow and Request Validation

The image below shows both the Authorization Workflow and use of OAuth Protected API calls.  As you can see, validating that a request is authorized by an OAuth token appears in both places.


What I wound up doing in my refactoring was:
  1. Creating an Authentication Filter to Validate Requests.  The OAuthFilter validates a request.  On failure, it returns a 401 Unauthorized error to the User Agent.  On success, it passes a wrapped request onto the web application that provides access to the authenticated user (the application), and the authorizing user, via the getUserPrincipal, getRemoteUser and isUserInRole API's of HttpServletRequestWrapper (and HttpServletRequest). 
  2. Creating a Servlet that handles the Data Holder's side of the Authorization Workflow (the Data Holder is the Server in 
  3. Creating a demonstration JSP Page to handle the "Authorization".

OAuthFilter

The OAuthFilter class is fairly simple.  It relies on two pluggable components, an OAuthProvider and a Repository.  The OAuthProvider class implements the verification and much of the Authorization Workflow. It delegates to the Repository to get access to tokens, users and applications.  These objects are structured according to the model shown below (it was the lack of this model that led to the complexity of my original Provider implementation).


During initialization, it constructs instances of specified OAuthProvider and Repository classes from the Filter initialization parameters, using defaults if they haven't been specified.

The filter protects all API pages, the OAuthServlet, and the Authorization page.  The first thing it does it put the OAuthProvider class into the Request context under the OAuth.Provider key.  This is how the OAuthServlet and the Authorization page get the provider it needs to use to manage the Authorization Workflow.

Next it checks to see if this is a request to the Authorization page.  If it is, it just forwards the request on to that page.  If it isn't then the filter must validate the request.  It extracts the OAuth parameters from the Authorization header, gets the tokens, and verifies that the signature is correctly computed.  If the tokens aren't there, the authorization header is missing, or the signature is incorrect, it returns a 401 Unauthorized to the caller, with a WWW-Authenticate: OAuth ream="realm" response header to the caller.

If the request is valid, the an OAuthRequestWrapper is created that will return an OAuthPrincipal in response to the getUserPrincipal() HttpServletRequest API call.

OAuthUser

This class represents the means by which any application can get access to the name (actually the identifier) of the Application which is making request, and the name of the User on whose behalf the requests are being made.  This principal represents not the User, but rather the Application, because it is the Application that is the actor doing things, not the user.  OAuthUser has one method that allows access to the id of the user being "impersonated" for the requests the application is performing (getImpersonating()).

Any API call protected by the OAuthFilter can obtain a reference to the OAuthUser class as follows:
  OAuthUser u = null;
  Principal p = request.getUserPrincipal();
  if (p instanceof OAuthUser)
u = (OAuthUser)p;

This is essential so that the API knows which user's data to return.

Repository

The repository provides access to Tokens (by their key), Users (by ID), and Applications (by ID), provides an API to construct tokens used in the Authentication Workflow, to remove used tokens, and to check and store nonce values.  Most tokens don't need persistent storage in a database, but client (or consumer) and access tokens need long term persistence.  For testing, I developed a repository that just stores all of these in memory.  Users and Applications do need long term persistence, and so the MemoryRepository class is only a starting point.

Nonces only need to be stored for about 5 minutes or so (depending on the maximum time difference between an application request and response you want to support).  I use two hash tables to deal with storing nonces.  The first one is populated with all nonces created in a particular 5 minute period.  The second one is populated with the nonces created in the prior period.  To check whether a nonce was already used, it is looked up in both tables, but when saving a nonce that has been used, it is stored in the first table. Every five minutes I throw away the oldest table, and transfer the newer table as the older table, and create a new empty table.  It's very quick, and means that I don't need to do much to manage nonces.

OAuthProvider

The OAuthProvider class supports a method to validate an HTTP Request, get tokens (from the repository) for each stage of the Authorization Workflow, check for prior authorization, authorize an application, and manage logging.

OAuthServlet

The OAuthServlet supports several steps of the Authorization Workflow.  It exposes two endpoints in the web application context: /oauth/request and /oauth/access.  The first endpoint is used by the client application to obtain a request token.  The second endpoint is used by the client application to convert a temporary token into an access token, which can be used in subsequent API requests.

The Servlet obtains the OAuthProvider to use from the request context (stored under the OAuth.Provider key by the OAuthFilter). It checks to see if the request is for a request token or access token, and also determines whether the request being performed is appropriate to the role associated with the tokens used in the request.  On failure here, the OAuthServlet returns a 404 Not Found error.

Otherwise, it performs the appropriate next step in the Authorization Workflow.

Authorization Page

The Authorization page is what connects OAuth to the rest of the web application.  This page has two halves.  The first half deals with fulfilling the Authorization Workflow.  The second half deals with displaying to the user what they can authorize for the application.  These are really two independent pieces, where only the second part should be customized by web applications.  I could refactor this just a bit more by placing some of this in the OAuthServlet, and having the Authorization page be configured so that the servlet could forward to it as needed.

The page expects an authorization request containing a request token coming from the user agent (via a redirect from the data holder).  When the page posts, it needs to include the roles that the application has been authorized to access on behalf of the authorizing user.

The Authorization page needs to be protected by container managed security.  This is because the OAuthProvider used by this page associates the user identified by the Principal associated with the request by container managed security in the application.  The ramification here is that Principal.getName() must be a unique identifier for each user.

Putting it All Together

OAuth enabling an application is pretty straightforward with the tools I've put together.

Here are the prerequisites:
  1. A JSP 2.3/Servlet 1.2 (or higher) enabled Web Container (my test Environment is Tomcat 5.0).
  2. A Web Application (existing site) that uses Container Managed Security to support login to your existing application.
And here are the steps to OAuth enable your application with these tools:
  1. Add the jar file containing the OAuthFilter and OAuthServlet to WEB-INF/lib in your application.
  2. Add about 50 lines to your applications web.xml to configure the OAuthFilter and OAuthServlet.
  3. Update the Authenticate.jsp page to support the roles needed by your application, and the user interface you want to present to users.
  4. Add the Authenticate.jsp page to the list of protected pages that require a login for your application.
  5. Create API pages that allow access to the services you want to expose.
  6. Ensure that your API pages are protected by the OAuthFilter.
All the code can be found here.  

Next Steps

My next steps are to build a SQL Database backed Repository, and do a little more thorough delivery on this code base (including creating appropriate Jar and documentation files which you have to build yourself today).  The DatabaseRepository will implement a bit more capabilities around users and applications, providing some of the attributes you saw in the class diagram at the beginning of this post.

2 comments:

  1. Curious, what's your take on using OAuth / IdP system for clinical care websites, eg tethered patient portals? Is there a HIPAA violation since the clinical system is likely to get an email address from the clinical system?

    ReplyDelete
    Replies
    1. Exchanges of PHI to healthcare providers by patients is not a HIPAA violation. It's how providers get PHI in the first place ;-)

      If the patient doesn't want the provider to get the e-mail address, all they need to is NOT authorize the access. Problem resolved.

      Delete