Implementing CapeDwarf’s Users API with PicketLink-Social’s OpenID support
JBoss CapeDwarf is an implementation of the Google App Engine API, which allows applications written for the Google App Engine to be deployed on JBoss Application Servers without modification. Behind the scenes, CapeDwarf uses existing JBoss APIs such as Infinispan, JGroups, PicketLink, HornetQ and others.
The Users API
One of the smallest APIs of the Google App Engine in terms of the number of API methods is the Users API. It basically contains only the following API methods: |getLoginUrl|, |getCurrentUser| and |getLogoutUrl|. By using the Users API, the application developer need not implement any kind of login system, because authentication is handled by AppEngine itself. Instead of directing the user to a custom login form, the application simply needs to point the user to the URL returned by |getLoginUrl|. This will allow the user to log in with either their Google account, their Google Apps Domain account or through an external OpenID provider. After the user logs in, they are redirected back to the application. The application can then obtain the logged-in user’s email address simply by calling the |getCurrentUser| API method.
In order to allow migration of existing GAE applications to and from CapeDwarf, CapeDwarf must also support using Google accounts and not introduce any custom method(s) of user authentication. Thanks to OpenID support in PicketLink Social this was pretty straightforward to achieve.
OpenID is an open standard that allows users to be authenticated by a trusted third party service. In CapeDwarf’s case, the third party service is the Google Accounts OpenID provider.
Using PicketLink Social to authenticate the user through Google Accounts is a very simple process.
Directing the user to the OpenID provider’s authentication page
When the user requests the login URL returned by |getLoginUrl|, CapeDwarf’s AuthServlet instantiates an instance of PicketLink Social’s |OpenIDManager|, associates it with CapeDwarf’s own implementation of |OpenIDProtocolAdapter| and then simply instructs the |OpenIDManager| to authenticate the user:
OpenIDManager manager = new OpenIDManager(new OpenIDRequest("https://www.google.com/accounts/o8/id"));
CapedwarfOpenIDProtocolAdaptor adapter = new CapedwarfOpenIDProtocolAdaptor(request, response, getReturnUrl(request));
OpenIDManager.OpenIDProviderList providers = manager.discoverProviders();
OpenIDManager.OpenIDProviderInformation providerInfo = manager.associate(adapter, providers);
manager.authenticate(adapter, providerInfo);
Behind the scenes, the manager calls the |OpenIDProtocolAdapter| and instructs it to redirect the user to the OpenID provider’s URL. The redirect is achieved either through a standard HTTP redirect or by sending a self-submitting HTML form to the browser (this is necessary when the OpenID payload is larger than 2048 bytes).
The OpenID provider then displays the login form to the user (or authenticates the user in some other way).
Handling the user’s return from the OpenID provider’s authentication page
After the user is authenticated successfully, the provider redirects the browser back to the consumer - CapeDwarf. This returning request is also handled by AuthServlet. The servlet verifies if the user is authenticated by calling verify() on the |OpenIDManager|. If the user has been authenticated, CapeDwarf can now access the email address of the authenticated user, store it in the session and redirect the browser to the destination URL that was supplied by the application when it requested the login URL in the first step.
boolean authenticated = manager.verify(adapter, getStringToStringParameterMap(request), getFullRequestURL(request));
if (authenticated) {
response.sendRedirect(request.getParameter(AuthServlet.DESTINATION_URL_PARAM));
}
During the invocation of the verify() method, the manager calls back our |OpenIDProtocolAdapter| with two types of |OpenIDLifecycleEvents|: |SESSION| and |SUCCESS|. The |SESSION| events instruct the adapter to store certain data in the session, while the |SUCCESS| event obviously signals that the authentication was successful.
The authentication process has now completed. Whenever the application now calls the User API’s |getCurrentUser| method, CapeDwarf will return the authenticated user’s email address. The email address simply identifies the user. It is up to the application to use this information whichever way it wants to.
How we handle application admins
On a final note, there is another API method that I haven’t mentioned yet: |isUserAdmin|. As is obvious from the method’s name, this method returns true if the logged in user is the admin of the application - either the Google user that uploaded the application to Google’s AppSpot or another user that was manually added as an admin by the application uploader through the Google Cloud Console.
Since there is no user account/email associated with deploying an AppEngine application to CapeDwarf, there is no notion of an admin. To specify who the app’s admins are, you need to list their email addresses in capedwarf-web.xml like this:
<capedwarf-web-app>
<admin>my.email@gmail.com</admin>
<admin>another.admin.email@gmail.com</admin>
</capedwarf-web-app>
Further info
For the complete source code of how CapeDwarf uses PicketLink Social, please turn to the Users module in CapeDwarf’s GitHub repo