How to Implement Facebook App Authorization in Java

Java Facebook

In this article we will go through the Facebook Application Authorization process that uses OAuth 2.0 signature scheme and implement it in Java. We will be creating a very simple iframe app that displays user's name for authorized users and forces unauthorized users to go through the authorization process.

Requirements

Assuming the iframe facebook app is already registered, we would need to have the following ready for the implementation:

  • Facebook App details: App ID, App Secret, Canvas Page, and Canvas URL. This information could be found on your app profile page.
  • Base 64 decoder with url-safe mode support. We will use Apache Commons Codec library.
  • JSON decoder. We will use Json-lib library.
  • HMAC SHA256 hashing implementation. Comes with JDK.
  • Facebook Graph API client. We will use RestFB library.

Authorization Workflow

Authoirzation process for facebook apps is described here. Our workflow would be the following:

  • With each request facebook sends signed_request parameter through HTTP POST to our Canvas URL that contains information about current user. If this parameter is not present, do a server side redirect to Canvas Page.
  • If user authorized the app, signed_request parameter contains user id and access token encoded according to the OAuth 2.0 signature scheme.
  • If user hasn't authorized the app, build OAuth Authorization Dialog url and do a client side redirect.

Implementation

Here is a facebook app authorization process implemented in Java using Spring Web MVC framework and Apache Velocity templates:

import java.net.URLEncoder;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

import org.apache.commons.codec.binary.Base64;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.RedirectView;

import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;
import com.restfb.types.User;

public class AppController extends AbstractController {

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("appView");

        //Facebook App info
        String fbSecretKey = "abcdefghijklmno";
        String fbAppId = "1234567890";
        String fbCanvasPage = "http://apps.facebook.com/example-app/";
        String fbCanvasUrl = "http://example.com/";

        //parse signed_request
        if(request.getParameter("signed_request") != null) {

            //it is important to enable url-safe mode for Base64 encoder 
            Base64 base64 = new Base64(true);

            //split request into signature and data
            String[] signedRequest = request.getParameter("signed_request").split("\\.", 2);

            //parse signature
            String sig = new String(base64.decode(signedRequest[0].getBytes("UTF-8")));

            //parse data and convert to json object
            JSONObject data = (JSONObject)JSONSerializer.toJSON(new String(base64.decode(signedRequest[1].getBytes("UTF-8"))));

            //check signature algorithm
            if(!data.getString("algorithm").equals("HMAC-SHA256")) {
                //unknown algorithm is used
                return null;
            }

            //check if data is signed correctly
            if(!hmacSHA256(signedRequest[1], fbSecretKey).equals(sig)) {
                //signature is not correct, possibly the data was tampered with
                return null;
            }

            //check if user authorized the app
            if(!data.has("user_id") || !data.has("oauth_token")) {
                //this is guest, create authorization url that will be passed to javascript
                //note that redirect_uri (page the user will be forwarded to after authorization) is set to fbCanvasUrl
                mav.addObject("redirectUrl", "https://www.facebook.com/dialog/oauth?client_id=" + fbAppId + 
                        "&redirect_uri=" + URLEncoder.encode(fbCanvasUrl, "UTF-8") + 
                        "&scope=publish_stream,offline_access,email");
            } else {
                //this is authorized user, get their info from Graph API using received access token
                String accessToken = data.getString("oauth_token");
                FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
                User user = facebookClient.fetchObject("me", User.class);
                mav.addObject("user", user);
            }

        } else {
            //this page was opened not inside facebook iframe,
            //possibly as a post-authorization redirect.
            //do server side forward to facebook app
            return new ModelAndView(new RedirectView(fbCanvasPage, true));
        }

        return mav;

    }

    //HmacSHA256 implementation 
    private String hmacSHA256(String data, String key) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKey);
        byte[] hmacData = mac.doFinal(data.getBytes("UTF-8"));
        return new String(hmacData);
    }

}

Page template:

<html>
    <head>
        #if($redirectUrl)
            <script>
                top.location.href = "${redirectUrl}";   
            </script>
        #end
    </head>
    <body>      
        Welcome, ${user.name}!
    </body>
</html>

Please note that if our page is viewed by an unauthorized user, we cannot simply do a server side redirect to the authorization dialog as our app is running inside an iframe. We would need to redirect the topmost window instead, which could be done with javascript.

Another important moment is that users will be redirected to Canvas Url page after authorization (as set by redirect_uri parameter in OAuth Dialog), so they will not be on facebook anymore. We can detect this redirect by the absence of signed_request request parameter and the presence of code (if a user clicked "Allow" button) or error (if a user clicked "Don't Allow" button) parameters, and forward a user back to our app inside facebook (Canvas Page). In this implementation we simply look at the absence of signed_request parameter and don't make a difference between a post-authorization redirect and a page being viewed not inside a facebook iframe.

Jul 23, 2011
profile for serg at Stack Overflow, Q&A for professional and enthusiast programmers