PermaLink Adding Google/Facebook/LinkedIn OAuth Support to DropWizard09/15/2013
DropWizard is a nice lightweight ops-friendly (provides health metrics) one-fat-jar (not a WAR file because it embeds jetty so it doesn't require a Java app server) Java REST web service framework that provides Basic authentication and OAuth (same server) authentication for your web services, but it doesn't provide 3rd-party OAuth authentication (e.g., logging in via your Facebook or Google+ ID).  OAuth authentication requires that you write your own OAuthProvider which isn't documented very well in the manual (where it says "Because OAuth2 is not finalized, this implementation may change in the future"), though there is one limited functionality sample of doing it.

As an alternative, there is a DropWizard example that supports OpenID, but OpenID isn't as flexible as the OAuth APIs.  OpenID primarily provides login/authentication.  OAuth on the other hand, also provides authorization for more rich APIs (e.g., Facebook's Opengraph and Google G+, where you can access a users contacts, albums, post to feed, etc. which is just as important as authentication in applications today), and can provide login/authentication if you add the email scope to your OAuth request (unless you're using Twitter for your authentication in which case you can't get the user's email address so it can't be used as an authentication source effectively).  To confuse matters more, Google also support OpenID, but if you're planning to use your backend for Android authentication, you'll have to use OAuth; you don't want to be migrating users after choosing OpenID by mistake like Player.FM did.

There was no Google/Facebook OAuth example for DropWizard, so I used Gary Rowe's OpenID example as a starting point since OpenID has a similar flow callbacks from the authorizing server. The next step was finding an OAuth library that handled most of the important OAuth providers.

Java OAuth Libraries
Surprisingly, there is no standard Java OAuth library. There are several at different levels:
- Scribe is the simplest OAuth library. It only helps you handle the token generation/processing part of OAuth. It doesn't provide any APIs to help you read contacts or post to feeds.
- SocialAuth is another OAuth library with plugins for handling Facebook/Twitter/LinkedIn contacts/feeds/albums, but surprisingly, there is no Google+ plugin (though OAuth w/ Google+ is supported). Google's OAuth Library might be able to be used to build the missing plugin.
- Spring Social from the Spring Framework is a framework that probably makes more sense to try integrating into Jacek's Spring Security DropWizard sample. Spring Social supports Facebook/Twitter/Linkedin including APIs for contacts/feeds/albums but Google+ support is 3rd party from Gabi Axel, but there were reasons for implementing the current providers.
If I had started w/ Jacek's Spring implementation, it would have made more sense to use Spring Social with Gabi Axel's very comprehensive provider (he even implemented Google Drive support). The simplest solution turned out to be SocialAuth, though I'll probably have to try writing a Google+ plugin if no one else has written one.

Customizing DropWizard's app.yml
The next thing to do was to add the OAuth configuration to DropWizard's YAML config file which is always passed in as a command line parameter. DropWizard uses YAML to avoid needing to use a Spring configuration file and it made sense to put all of SocialAuth's configuration into this file instead of having a Java .properties file for users to edit.

Looking at SocialAuth's properties file, each OAuth method has basically three properties:
graph.facebook.com.consumer_key = 152190004803645
graph.facebook.com.consumer_secret = 64c94bd02180b0ade85889b44b2ba7c4
graph.facebook.com.custom_permissions = publish_stream,email

so these are basically, the key, secret, and permissions. I wanted my corresponding YAML file to look like this:
oauthCfg:
- name: googleplus
prefix: googleapis.com
key: 11111111.apps.googleusercontent.com
secret: aabbccddee
permissions: https://www.googleapis.com/auth/userinfo.profile,email
- name: facebook
prefix: graph.facebook.com
key: 123456
secret: ababababababab
permissions: user_photos,email,offline_access

The root is oauthCfg, and under that, I wanted an array of OAuth configs. Each config consists of a name, the prefix for the original property names, the key, secret, and permissions (named for brevity). While generating the Properties object to feed into SocialAuth, I generated the appropriate property names. Setting up the configuration java file for this wasn't obvious because the DropWizard manual only describes how to do simple strings.

To do the array part of this, you declare the array as a List, then declare what kind of object the List contains:
@JsonDeserialize(contentAs = OAuthCfgClass.class)
private List<OAuthCfgClass> oauthCfg;
The declaration of the OAuthCfgClass is similar to what the manual describes:
public static class OAuthCfgClass {
@JsonProperty private String url;
@JsonProperty private String name;
@JsonProperty private String prefix;
@JsonProperty private String key;
@JsonProperty private String secret;
@JsonProperty private String permissions;
....// getters
}

The rest of the SocialAuth properties file contains a few properties that aren't so regular:
socialauth.myprovider = org.my.provider.GoogleImpl
proxy.host=YOUR HOST NAME(e.g. 127.0.0.1)
proxy.port=PORT(e.g.8888)
http.connectionTimeOut = TIMEOUT(e.g. 5000)


These are added via a YAML like this:
oauthCustomCfg:
socialauth.myprovider: org.my.socialauth.provider.SomethingImpl
socialauth.myprovider.scope: user_email
and declared in the configuration Java file with a HashMap so duplicates property names are automatically handled:
@JsonProperty
private HashMap<String, String> oauthCustomCfg = null;
And then they'll be added to the Properties object that is passed to SocialAuth.
A similar construct is used for the adminUsers.

Connecting SocialAuth to DropWizard
Once SocialAuth's configurations could be brought in, it was fairly simple to hook the actual OAuth authentication flow into DropWizard. This is done in the src/main/java/resources/PublicOAuthResource.java file. As with OpenID, there's an /oauth/request endpoint and /oauth/verify.

The /oauth/request is called via a web page login button for a specific OAuth provider, then it calls SocialAuth for the URL to redirect the user to where the OAuth provider will present a confirmation page w/ the requested permissions. The /oauth/verify endpoint (this is set in the app.yml file as oauthSuccessUrl) is what the user is sent back to locally when the user confirms that dialog page; if the user is valid, the user profile is created and the user is placed into the usercache which is also used by OpenID logins. The user's email address/provider are also checked to see if they should be given the "admin" role.

Unfortunately, this is a case where we find out that OAuth isn't as standardized as OpenID. With OAuth, some providers (Twitter) won't return the user's email address to you so you can't use them as an authentication source; Twitter's user profile also doesn't supply Firstname/Lastname fields...only a Fullname field. I added code to generate Firstname/Lastname from Fullname for Twitter, but I still don't recommend using Twitter for an OAuth provider because of the email address issue.

Adding Social Data to Models
Social Data can be added by using SocialAuth's plugins; examples for loading contacts/albums are in the BaseModel's loadContacts and loadAlbums methods. Contacts are loaded with:
AccessGrant grant = getUser().getOAuthInfo();
AuthProvider provider = AuthProviderFactory.getInstance(grant.getProviderId(),
OpenIDDemoService.getConfig().getOAuthCfgProperties());
provider.setAccessGrant(grant);
provider.registerPlugins(); // this activates the plugins for this provider
contacts = provider.getContactList();
When the user is verified, the AccessGrant is stored w/ the user object which can be serialized into a database. The AccessGrant is all that SocialAuth needs to reconstitute the OAuth authorization and use that with a plugin to access more information from that social network as you can see in the code above.

Unfortunately, this is another case where you also have to be careful about what information you use from each OAuth provider, even though SocialAuth tries to provide APIs that makes contacts/albums/feeds behave similarly for all providers. E.g., LinkedIn returns private contacts (users w/ first/last names of "private" and "private") to you or users without links to their profiles. I tried to filted these out so a more consistent model is sent to FreeMarker templates.

Displaying Social Data in FreeMarker
Once you add the data to the model, displaying the model in FreeMarker is fairly simple:
<#if model.contacts??>
<div class="row">
<h2>Social Contacts</h2>
<#list model.contacts as c>
<div class="col-sm-4 col-md-3">
<div class="thumbnail">
<#if c.profileImageURL??>
<a href="${c.profileUrl}"><img src="${c.profileImageURL}"
alt="${c.displayName}"></a>
</#if>
<a href="${c.profileUrl}">${c.displayName}</a>
</div>
</div>
</#list>
</div>
</#if>
One caveat is that some contacts don't have profile images, so you have to handle this case in the FreeMarker template when you display the contact. Also, note that you don't want to display contacts/albums this way in a production application; the data should be fetched asynchronously so that your users don't have to wait 5sec+ for their FreeMarker template to load.

Gradle and Eclipse
If you use Eclipse and try to import this dropwizard-oauth-openid project, the import process will hang if you have the useful Lombok plugin installed; you have to comment it out of your eclipse.ini until the Eclipse Gradle plugin is fixed and if you ever have to rebuild the Gradle models for the project. Also, you'll also have to comment out the shadow support (used to build the project into a single jar) so you can avoid an error with the create method.

What's Next
Looks like what's next is writing a Google+ plugin for SocialAuth. I haven't been able to find one, which is really odd given how popular Google+ is now. If anyone knows of one, please leave a msg in comments...

UPDATE: As of 9/16/2013, the code for the Google+ Feed/Albums plugin is in SocialAuth's github
http://blog.silb.no/shiro-securing-dropwizard.html

Comments :v

1. Tanmoy Bhattacharjee09/08/2015 11:18:43


Hi,
The piece of code was really a gem. Do u have any test classes to hit the oauth/verify and oauth/request for testing purpose?

Thanks in advance
Tanmoy




2. Simon Hopkins09/06/2014 20:46:28


Awesome piece of code - really helped me around the authentication issue - major Thank you!
I do have one question... socialAuth requires the use of sessions to store the manager. In your example you use the yammer 0.6 version of Dropwizard which supports a session handler. The 0.7 standard version has no such support - consequently I have cluged my way around saving off the SocialAuthManager object.
Do you have advice re: switching to the yammer version of Dropwizard, implementing my own session management or saving off the SocialAuthManager object between requests in some other way.
Thanks again
Simon




Start Pages
RSS News Feed RSS Comments Feed CoComment Integrated
The BlogRoll
Calendar
November 2024
Su
Mo
Tu
We
Th
Fr
Sa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Search
Contact Me
About Ken
Full-stack developer (consultant) working with .Net, Java, Android, Javascript (jQuery, Meteor.js, AngularJS), Lotus Domino