Go to content

Retrieve Facebook profile data in Java: Apache Oltu

Published on

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 OAuthJSONAccessTokenResponse.

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();

  OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(
    oAuthClientRequest, OAuthJSONAccessTokenResponse.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.