Retrieve Facebook profile data in Java: Apache Oltu

In previous blogposts I explained how you can get Facebook profile data using Scribe or Spring Social for the OAuth 2 handling. In this article you can read how to get the profile using Apache Oltu (formerly known as Apache Amber).

Update: browsing through the source of Apache Oltu I found an enum for the provider endpoints which wasn’t mentioned in the documentation.

Setup

The Maven dependency for the Apache Oltu OAuth client:

<dependency>
  <groupId>org.apache.oltu.oauth2</groupId>
  <artifactId>org.apache.oltu.oauth2.client</artifactId>
  <version>0.31</version>
</dependency>

The setup for the controller:

@Controller
public class FacebookOltuAuthenticator {

  private final String clientId;
  private final String clientSecret;
  private final String applicationHost;
  private final ObjectMapper objectMapper;

  @Autowired
  public FacebookOltuAuthenticator(@Value("#{properties['facebook.clientId']}")
                                     String clientId,
                                   @Value("#{properties['facebook.clientSecret']}")
                                     String clientSecret,
                                   @Value("#{properties['application.host']}") 
                                     String applicationHost) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.applicationHost = applicationHost;
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new AfterburnerModule());
  }
}

Start the authentication

Scribe and Spring Social have classes that know the endpoint for the authorization. Currently Apache Oltu doesn’t have such convenience classes which means we have to provide the URL for the authorization endpoint. Apache Oltu has an enum OAuthProviderType for authorization and token endpoints of common OAuth 2 providers.

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(OAuth.OAUTH_STATE, state);
  OAuthClientRequest oAuthClientRequest = OAuthClientRequest
      .authorizationProvider(OAuthProviderType.FACEBOOK)
      .setClientId(clientId)
      .setRedirectURI(applicationHost + "/auth/facebook/callback")
      .setParameter(OAuth.OAUTH_STATE, state)
      .buildQueryMessage();

   return new RedirectView(oAuthClientRequest.getLocationUri());
}

Handle the callback

Apache Oltu has a helper class to get the authorization response from the HttpServletRequest, which means we don’t have to define the code and state parameters in the method signature.

@RequestMapping("/auth/facebook/callback")
public RedirectView callback(HttpServletRequest request, HttpSession session) 
    throws IOException, OAuthSystemException, OAuthProblemException {
  String stateFromSession = (String) session.getAttribute(OAuth.OAUTH_STATE);
  session.removeAttribute(OAuth.OAUTH_STATE);

  OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
  String stateResponse = oar.getState();

  if (StringUtils.isBlank(stateResponse) || 
       !stateResponse.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
  String accessToken = getAccessToken(oar, oAuthClient);

  OAuthResourceResponse resourceResponse = 
      getFacebookProfileResponse(oAuthClient, accessToken);
  if (resourceResponse.getResponseCode() != HttpServletResponse.SC_OK) {
    return new RedirectView("/login");
  }

  String facebookUserId = getFacebookUserId(resourceResponse);
  session.setAttribute("facebookUserId", facebookUserId);

  return new RedirectView("/logged-in");
}

Retrieve the AccessToken

Apache Oltu has two different classes to parse the access token response. Facebook’s response is not fully compliant with the final version of the OAuth 2 specification, but it can be parsed using the class GitHubTokenResponse.

We have to tell Apache Oltu which URL can exchange the authorization code for an access token, because there’s no Facebook specific implementation yet.

private String getAccessToken(OAuthAuthzResponse oar, OAuthClient oAuthClient)
    throws OAuthSystemException, OAuthProblemException {
  String code = oar.getCode();
  OAuthClientRequest oAuthClientRequest = OAuthClientRequest
    .tokenProvider(OAuthProviderType.FACEBOOK)
    .setGrantType(GrantType.AUTHORIZATION_CODE)
    .setClientId(clientId)
    .setClientSecret(clientSecret)
    .setRedirectURI(applicationHost + "/auth/facebook/callback")
    .setCode(code)
    .buildQueryMessage();

  GitHubTokenResponse oAuthResponse = oAuthClient.accessToken(
    oAuthClientRequest, GitHubTokenResponse.class);
  return oAuthResponse.getAccessToken(); 
}

Get the Facebook profile data

First we get the profile as JSON:

private OAuthResourceResponse getFacebookProfileResponse(
    OAuthClient oAuthClient, String accessToken)
    throws OAuthSystemException, OAuthProblemException {
  OAuthClientRequest bearerClientRequest =
      new OAuthBearerClientRequest("https://graph.facebook.com/me")
          .setAccessToken(accessToken).buildQueryMessage();

   return oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET,
      OAuthResourceResponse.class);
}

Just like we did with Scribe, we use Jackson to extract the user id from the JSON response:

private String getFacebookUserId(OAuthResourceResponse resourceResponse) 
    throws IOException {
  String resourceResponseBody = resourceResponse.getBody();
  JsonNode jsonNode = objectMapper.readTree(resourceResponseBody);
  JsonNode idNode = jsonNode.get("id");
  return idNode.asText();
}

Conclusion

As you can see, a lot more knowledge of the OAuth 2 flow and the specific endpoints are is needed to make this implementation with Apache Oltu. It would help developers if convenience classes are added for the larger OAuth 2 providers as Facebook, Google etc. On the other hand it does help you understand better how the flow works. When you have implemented it once, you can add any other OAuth 2 provider by changing endpoints and the way the profile is parsed from the JSON response.

Retrieve Facebook profile data in Java: Spring Social

In the previous blogpost I explained how you can get Facebook profile data using Scribe. This blogpost will do the same for Spring Social.

Setup

The Maven dependency for Spring Social Facebook:

<dependency>
  <groupId>org.springframework.social</groupId>
  <artifactId>spring-social-facebook</artifactId>
  <version>1.0.3.RELEASE</version>
</dependency>

Setup of the Spring controller:

@Controller
public class FacebookSpringSocialAuthenticator {
  public static final String STATE = "state";
  private String applicationHost;
  private FacebookConnectionFactory facebookConnectionFactory;

  @Autowired
  public FacebookSpringSocialAuthenticator(
      @Value("#{properties['facebook.clientId']}") 
        String clientId,
      @Value("#{properties['facebook.clientSecret']}") 
        String clientSecret,
      @Value("#{properties['application.host']}") 
        String applicationHost) {
    this.applicationHost = applicationHost;
    facebookConnectionFactory = 
      new FacebookConnectionFactory(clientId, clientSecret);
  }
}

Start the authentication

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(STATE, state);

  OAuth2Operations oauthOperations = 
      facebookConnectionFactory.getOAuthOperations();
  OAuth2Parameters params = new OAuth2Parameters();
  params.setRedirectUri(applicationHost + "/auth/facebook/callback");
  params.setState(state);

  String authorizeUrl = oauthOperations.buildAuthorizeUrl(
      GrantType.AUTHORIZATION_CODE, params);
  return new RedirectView(authorizeUrl);
}

Handle the callback

@RequestMapping("/auth/facebook/callback")
public RedirectView callBack(@RequestParam("code") String code,
                             @RequestParam("state") String state,
                             HttpSession session) {
  String stateFromSession = (String) session.getAttribute(STATE);
  session.removeAttribute(STATE);
  if (!state.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  AccessGrant accessGrant = getAccessGrant(code);

  String facebookUserId = getFacebookUserId(accessGrant);
  session.setAttribute("facebookUserId", facebookUserId);
  return new RedirectView("/logged-in");
}

Retrieve the AccessGrant

private AccessGrant getAccessGrant(String authorizationCode) {
  OAuth2Operations oauthOperations = 
      facebookConnectionFactory.getOAuthOperations();
  return oauthOperations.exchangeForAccess(authorizationCode,
      applicationHost + "/auth/facebook/callback", null);
}

Get the Facebook user id

Spring social splits up the profile response into several classes. With Connection#fetchUserProfile you get the username, but not his identifier, which is retrieved with Connection#getKey.

private String getFacebookUserId(AccessGrant accessGrant) {
  Connection<Facebook> connection = 
      facebookConnectionFactory.createConnection(accessGrant);
  ConnectionKey connectionKey = connection.getKey();
  return connectionKey.getProviderUserId();
}

Conclusion

Spring Social has built in methods to convert the JSON response of the profile into Java objects which Scribe doesn’t do for you. Its setup is a bit simpler and there are no traces of OAuth 1 support in the Facebook classes. Spring social seems to handle the flow a bit slower (200-300 ms slower) than Scribe.

Retrieve Facebook profile data in Java: Scribe

In a series of blogposts I’ll describe how to retrieve a Facebook profile using several OAuth2 libraries for Java and Spring MVC. The first example is for Scribe.

Setup

The Maven dependency for Scribe:

<dependency>
    <groupId>org.scribe</groupId>
    <artifactId>scribe</artifactId>
    <version>1.3.5</version>
</dependency>

First you need to register an application with Facebook. Then add an HTML snippet to your site that triggers the login flow:

<a href="/auth/facebook">Get my Facebook profile</a>

We create a Spring Controller and inject the Facebook client id, client secret and the host of our application:

@Controller
public class FacebookScribeAuthenticator {

  public static final String STATE = "state";
  private String applicationHost;
  private OAuthService oAuthService;
  // Jackson ObjectMapper
  private ObjectMapper objectMapper;

  @Autowired
  public FacebookScribeAuthenticator(
      @Value("#{properties['facebook.clientId']}") 
      String clientId,
      @Value("#{properties['facebook.clientSecret']}") 
      String clientSecret,
      @Value("#{properties['application.host']}") 
      String applicationHost) {
    this.applicationHost = applicationHost;
    this.oAuthService = buildOAuthService(clientId, clientSecret);
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new AfterburnerModule());
  }
}

private OAuthService buildOAuthService(String clientId, 
                                       String clientSecret) {
  // The callback must match Site-Url in the Facebook app settings
  return new ServiceBuilder()
      .apiKey(clientId)
      .apiSecret(clientSecret)
      .callback(applicationHost + "/auth/facebook/callback")
      .provider(FacebookApi.class)
      .build();
  }
}

Start the authentication

Now we add a @RequestMapping to start the OAuth2 authorization flow. To prevent CSRF we add and store a state parameter that should be returned by Facebook. Scribe doesn’t handle this parameter in its API, so we have to add it to the authorization URL.

@RequestMapping("/auth/facebook")
public RedirectView startAuthentication(HttpSession session) 
    throws OAuthSystemException {
  String state = UUID.randomUUID().toString();
  session.setAttribute(STATE, state);
  String authorizationUrl = 
      oAuthService.getAuthorizationUrl(Token.empty()) 
        + "&" + STATE + "=" + state;
  return new RedirectView(authorizationUrl);
}

When the user goes to /auth/facebook, he is redirected to the Facebook authentication URL. If it’s his first visit, he has to approve the access to his data for your application:

Facebook approval

Handle the callback

After the user has approved the access to his profile, he is redirected to our callback endpoint with two request parameters: code and state. The code is the authorization code that can be exchanged for an access token. The state parameter should have the same value as we have sent to Facebook in the authorization request.

In case of failure we redirect the user to /login. If we can retrieve the user’s Facebook user id successfully, he is redirected to /logged-in.

@RequestMapping("/auth/facebook/callback")
public RedirectView callback(@RequestParam("code") String code,
                             @RequestParam(STATE) String state,
                             HttpSession session) 
      throws IOException {
  // Check the state parameter
  String stateFromSession = (String) session.getAttribute(STATE);
  session.removeAttribute(STATE);
  if (!state.equals(stateFromSession)) {
    return new RedirectView("/login");
  }

  // Exchange the code for an AccessToken and retrieve the profile
  Token accessToken = getAccessToken(code);
  Response response = getResponseForProfile(accessToken);
  if (!response.isSuccessful()) {
    return new RedirectView("/login");
  }

  // Store the Facebook user id in the session and redirect the user
  // to the page that needs the profile.
  String facebookUserId = getFacebookUserId(response);
  session.setAttribute("facebookUserId", facebookUserId);
  return new RedirectView("/logged-in");
}

Retrieve the AccessToken

The Token.empty() is passed because Scribe’s OAuthService handles both OAuth1 and OAuth2 providers.

private Token getAccessToken(String code) {
  Verifier verifier = new Verifier(code);
  return oAuthService.getAccessToken(Token.empty(), verifier);
}

Get the Facebook profile data

Anyone can access the public profile data for https://graph.facebook.com/<userId>, but you need to be logged in to get https://graph.facebook.com/me

private Response getResponseForProfile(Token accessToken) {
  OAuthRequest oauthRequest = 
      new OAuthRequest(Verb.GET, "https://graph.facebook.com/me");
  oAuthService.signRequest(accessToken, oauthRequest);
  return oauthRequest.send();
}

Facebook will return the profile as JSON (I know, this is not my profile):

{
 "id":"4",
 "name":"Mark Zuckerberg",
 "first_name":"Mark",
 "last_name":"Zuckerberg",
 "link":"https:\/\/www.facebook.com\/zuck",
 "username":"zuck",
 "gender":"male",
 "locale":"en_US"
}

We use Jackson to get the Facebook identifier from the JSON response:

private String getFacebookUserId(Response response) 
    throws IOException {
  String responseBody = response.getBody();
  JsonNode jsonNode = objectMapper.readTree(responseBody);
  JsonNode idNode = jsonNode.get("id");
  return idNode.asText();
}

Now you have the Facebook user id in the HttpSession. In the next blogposts I’ll explain how to do this with other Java libraries.

WidgetGap icon

WidgetGap: a Wookie & Cordova (PhoneGap) mashup widget

I knew PhoneGap as a tool to make native mobile apps from HTML, CSS and JavaScript, but had never used it myself. When I had to debug a PhoneGap application I read that the project you’re uploading is basically a W3C widget. I should have known from Scott’s blogpost, but apparently I forgot. I did remember that Apache Wookie is responsible for rendering W3C widgets in Apache Rave.

WidgetGap iconI decided to make my own demo widget called WidgetGap, that can be used for making both a mobile app and a widget in Rave. Then I started a new iOS project for Apache Cordova (the open source project behind PhoneGap). If you don’t develop in OSX, pick a getting started guide that works on your machine.

I copied the result of a JSON call to the REST API of the Hippo Go Green demo in a Javascript variable to process that into the HTML. I chose this local variable because I didn’t want to call the REST interface over and over again just to get my markup right. After modifying the CSS it was time to replace the local resultset with a real AJAX call. The app and Wookie widget refuse to fetch the data because it’s a call to a different domain. Luckily there are solutions for this. For the mobile app you should add the following entry to the config.xml

<access origin="http://www.demo.onehippo.com"/>

For the iPhone simulator you also need to add this domain to the “External Hosts” entry in Resources/Cordova.plist file in the Xcode project.

For the Wookie widget I still didn’t get the data, but a few extra lines of Javascript made it work:

var loc = productLink;
if (window.widget && typeof window.widget.proxify == 'function') {
    loc = widget.proxify(loc);
}

Apart from issues with DOM manipulations in Javascript (not my expertise), I was able to create the widget that displayed a list of products from the Go Green REST interface with more detailed information when you click on the title.

Try it

The mobile app is available on PhoneGap (except for iOS because of Apple’s licensing policy).

To see the widget in Wookie, download and start the standalone version of Wookie.
In a different shell clone the WidgetGap repository from Github and go to the www folder. Package the contents and move this package to the deployment directory of the running Wookie instance:

git clone git://github.com/jashaj/WidgetGap.git
cd WidgetGap/www
zip -r widgetgap.wgt *
mv widgetgap.wgt ~/apache-wookie-0.11.0-incubating-standalone/build/webapp/wookie/deploy/

In the shell where you started Wookie you see the feedback about the deployment. If you see “‘WidgetGap’ – Widget was successfully imported into the system as WidgetGap” in the shell, you can view the widget it in your browser on http://localhost:8080/wookie/demo/.

The code is not optimised at all and maybe there is a better way to create this widget, but I wanted to show that you can write a single widget for the web and build a mobile app from it.

Fronteers 2012

Even though most of the web development I do now is in the backend, I started with HTML in 1995 before I learned programming Java. In 2010 I attended the Fronteers conference and came back excited about the new CSS and Javascript tricks. This is why I wanted to go to the Fronteers 2012 conference this year. Instead of giving a full coverage of the conference, I’ll give my tweets a little context where needed.

General

Saving a lot of interesting links from #fronteers12 presentations on delicious.com/jashaj

RT @sjoerdly: Still great work on the collaborative notes j.mp/RezoFp top initiative by @hayify #fronteers12 even with @alexgraul talking speed.

The beautiful venue of #fronteers12 Tuschinski twitpic.com/b16dgt Lights in Tuschinski 1

Thanks @FronteersConf for a great conference. @codepo8 you’re not only an inspiring speaker, but also a good MC.

A Pixel is not a Pixel by Peter-Paul Koch

Pixels and viewports explained by @ppk #fronteers12 Finally learning what the meta tag means I’ve been copy pasting the last 2 years.
PPK explained the difference between various widths you browser uses to calculate the size of boxed. You’ve probably used something like <meta name=”viewport” content=”width=device-width”> in your HTML to let your (mobile) device render the HTML nicely.

Interesting: (over-)usage of javascript libraries for just a few simple tasks eat up your mobile phone’s battery life. #fronteers12
The lazy way to handle simple tasks like selecting a specific DOM element or doing an Ajax call is to include a framework such as jQuery. This brings in a lot of code your site is not using. For a desktop device behind a broadband network this is not a real issue. For the mobile web it means not only a long loading time over a 2G network, but also a shorter battery life for your device because it has to process all that code.

Ten things I didn’t know about HTML by Mathias Bynens

<3 legacy in HTML5 <html>
<body bgcolor=”chucknorris”>
<h1>Chuck Norris rules</h1>
</body>
</html> #fronteers12

HTML5 specifies how browsers should behave in case of incorrect markup. In case of bgcolor=”chucknorris” it finds 2 valid hexadecimal characters and adds four 0′s to get a valid value. The result is the same as bgcolor=”#CC0000″> (red)

Browsers are just (too) kind for people creating webpages. Try omitting a ; in Java code and it won’t compile. #fronteers12
Mathias was talking about a discussion on github whether to add an optional ; at the end of a Javascript statement. Browsers have always accepted invalid HTML and this is IMO the main reason everyone was able to create their own webpage.

Accessibility panel with Bram Duvigneau, Antoine Hegeman and Bor Verkroost

Very useful to hear what issues people can have wrt accessibility of websites. #fronteers12
When the video of this discussion panel becomes available, I’d recommend you watching it. Not everyone can handle menus with a lot of items or a list of links too close to each other.

RT @rubenbos: Blind developer (!) demoing how he browses the web at #fronteers12. We can/should learn from this. #accessibility pic.twitter.com/n6ntQQTk Accessibility discussion panel at Fronteers 2012

More CSS secrets: Another 10 things you may not know about CSS by Lea Verou

The result of the circling smiley is that I get hypnotised with a smile on my face :). Very cool CSS tricks #fronteers12
We saw 10 (amazing) effects you can create with modern CSS3 without Javascript. One of them was a smiley circling on the page.

The biggest devils in the smallest details by Marcin Wichary

Apparently there is a team of designers and developers that work on the Google doodles for a living. As I write this, Google shows the doodle for Niels Bohr‘s 127th birthday.

my aunt called me when she saw the Pacman doodle. She thought her computer was infected by a virus. #fronteers12 and RT @flurin: Pacman doodle scared people because their computers suddenly started to make noise. They fixed it during the day. @mwichary #fronteers12
During the day that doodle was changed because my aunt wasn’t the only one.

Presenter’s MacBook drops and survives #fronteers12
550 hearts skipped a beat when the presenter’s MacBook fell off the lectern during the presentation. Surprisingly it wasn’t damaged.

Building the web platform by Anne van Kesteren

LOL: What the WTF: wtw.tf #fronteers12

I can smell your CMS by Phil Hawksworth

Next on #fronteers12: Phil Hawksworth, I can smell your CMS. Very interested if my former employer smells nice or not.
My former employer did very well actually. It lets you create your own URL scheme so you can migrate from/to an other platform without being forced to use other URLs. It doesn’t enforce HTML output. Its WYSIWYG editor is outdated but eat least it has a means to clean its output so you won’t leave empty paragraphs or font elements.

Hey, someone with a laptop that is not a MacBook #fronteers12
The vast majority was using devices that were designed in Cupertino. After 1,5 day I saw the first laptop from a different vendor. Later that day there turned out to be 5 of them in an audience of 550 people. Who thinks different?

OMG this really smells: instead of burton.com -> eur.burton.com/on/demandware.store/Sites-Burton_EU-Site/default #cmssmell #fronteers12
This site has a ridiculously long URL for this site’s homepage that also contains the name of the CMS vendor in it.

That view state is default behaviour of ASP.Net (still ugly) #fronteers12 #cmssmell
An enterprise CMS vendor has this very long hidden input field on its own site. My .NET knowledge is a bit outdated because someone says Microsoft changed this default behaviour years ago.

WYSIWYG is danger, but customers demand it #fronteers12 #smellcms

If people still think a post processor to clean WYSIWYG crapTML is not necessary, watch the video of this talk #smellcms #fronteers12
We saw traces of empty paragraphs with a non-breaking space wrapped in a font element to give it a smaller size.

Please speak up a bit during the #fqa #fronteers12
Instead of passing a microphone through the audience, the MC did the QA based on tweets in a 1-on-1 session. Because the MC and the speaker were close to each other, they forgot to speak up for the rest of the audience.

RT @philhawksworth: 149 stinking slides from my #smellCMS session at #fronteer12 are now available at speakerdeck.com/u/philhawkswor

Shameless plug

Turn a banana into an input device for your computer makeymakey.com #fronteers12

What the legacy web is keeping from us by Alex Russell

Features IE7 users are missing. It has been 6 years since that browser has been released. #fronteers12 twitpic.com/b19nmg Summary of features IE7 users cannot use

Don’t blame the corporate users for their outdated browsers. Blame the IT department and CIO #fronteers12 #fqa
As web developers we’d like users to upgrade to the latest browser versions to use CSS instead of Javascript or images for cool effects. Employees of larger corporations are usually stuck with outdated Internet Explorer version and they cannot upgrade themselves. It’s the IT department that is afraid to roll out updates of software or they don’t get the time to work on it. There’s also Microsoft to blame to be so slow releasing new versions of IE. It’s easier to roll out small updates than the big-bang upgrades Microsoft comes with.