Go to content

Retrieve Facebook profile data in Java: Scribe

Published on

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 does not 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, they are redirected to the Facebook authentication URL. If it’s their first visit, they have to approve the access to their data for your application:

Facebook’s consent screen

Handle the callback

After the user has approved the access to his profile, they are 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, they are 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

You need a valid access token to get the user’s profile on 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.