How to Implement Facebook App Authorization in Java

JavaFacebook

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

How to Compile and Run Java Code from a Command Line

Java

Being spoiled by IDEs and automated building tools I recently realized that I don't know how to run java code from a command line anymore. After playing a guessing game for an hour trying to compile a simple piece of code that took 5 minutes to write, I thought maybe it's time to do a little research.

Task

Lets say we have a fairly standard java project that consists of three top level folders:

/bin - empty folder that will contain compiled .class files

/lib - contains third party .jar files

/src - contains .java source files

Our task would be to compile and launch the project from its root folder. We will use Windows OS as example (on Unix systems the only difference would be path separation symbol - ":" instead of ";").

Compiling Java Code

The first step is compiling plain text .java sources into Java Virtual Machine byte code (.class files). This is done with javac utility that comes with JDK.

Assuming we are at the application root folder trying to compile Application.java file from com.example package that uses lib1.jar and lib2.jar libraries from lib folder to a destination bin folder, compilation command should have the following format:

javac -d bin -sourcepath src -cp lib/lib1.jar;lib/lib2.jar src/com/example/Application.java

As a result bin/com/example/Application.class file should be created. If Application.java uses other classes from the project, they all should be automatically compiled and put into corresponding folders.

Running Java Code

To launch a .class file we just compiled, another JDK utility called java would be needed.

Assuming we are at the application root folder trying to launch Application.class file from com.example package that uses lib1.jar and lib2.jar libraries from lib folder, the launch command should have the following format

java -cp bin;lib/lib1.jar;lib/lib2.jar com.example.Application

Note that we don't provide a filename here, only an actual class name that java would attempt to find based on provided classpath.

Some Notes About Classpath

Lets say during Application.java compilation a compiler stumbles upon some com.example.Util class. How to find it in the file system? According to Java file naming rules, Util class has to be located somewhere in Util.java file under /com/example/ folder, but where to start searching for this path? Here is where classpath comes into play which sets the starting folder for searching for classes. Classpath can be set in 3 different ways:

  • If no --classpath parameter is passed, CLASSPATH environment variable is used
  • If CLASSPATH environment variable is not found, current folder (".") is used by default
  • If --classpath is explicitly set as a command line parameter, it overrides all other values

The fact that classpath when set overrides default value (current folder) can cause some unexpected results.

For example if we don't use any third party libraries, only our own com.example.Util class, and try to compile Application.java from the src folder:

javac com/example/Application.java

this would work, but then if we decide to add a third party libarary to the classpath:

javac -cp lib/lib1.jar com/example/Application.java

it would cause an error:

package com.example.Util does not exist

This happens because when we set -cp lib/lib1.jar we override default value for the classpath - current folder. Now a compiler will be looking for all classes only inside that jar file. To fix this we need to explicitly add the current folder to the classpath:

javac -cp .;lib/lib1.jar com/example/Application.java
Jul 9, 2011

How to Post on App Wall as Admin from Facebook API

JavaFacebookJavaScript

In this article we will use Facebook API to post automatic updates on a fan page wall on behalf of the page itself (without showing author's name). We will look into two different scenarios - posting to an application wall and posting to a separated fan page.

Main challenge in both cases would be getting permanent access token that will let us act on behalf of a page admin, the rest is very similar to making a regular wall post. Code examples will be given for JavaScript API and Java RestFB API.

Posting To Facebook Application Wall As Admin

Application wall is the one that comes "for free" with your FBML or iframe facebook application. Its URL looks like this:

http://www.facebook.com/apps/application.php?id=<APP_ID>&v=wall

Getting access token in this case is pretty simple. All we need is open the following link in a browser (as described here in "Authenticating as an Application" section):

https://graph.facebook.com/oauth/access_token?type=client_cred&client_id=<APP_ID>&client_secret=<APP_SECRET>

This should return permanent access token we can use for posting.

In order to post on the App Wall we will need to send HTTP POST request to following connection URL:

https://graph.facebook.com/<APP_ID>/feed 

and provide message, picture, link, name, caption, description, source parameters as described here, not forgetting to send our access token parameter that we just retrieved.

Posting to App Wall using JavaScript API:

//API init code is omitted
var wallPost = {
    access_token: "<ACCESS_TOKEN>",
    message: 'Hello, World!'
};

FB.api('/<APP_ID>/feed', 'post', wallPost, function(response) {
    if (!response || response.error) {
        alert('Error occurred');
    } else {
        alert('Success!');
    }
});

Corresponding Java API code would look like:

FacebookClient facebookClient = new DefaultFacebookClient("<ACCESS_TOKEN>");
FacebookType publishMessageResponse = facebookClient.publish("<APP_ID>/feed", FacebookType.class, Parameter.with("message", "Hello, World!"));

Now if you go to your app wall you should see your message posted.

Posting To Facebook Fan Page Wall As Admin

Fan page is a page that a user has created through "Create Page" dialog. Its URL has the following format:

http://www.facebook.com/pages/<PAGE_NAME>/<PAGE_ID>?v=wall

Posting to this wall on behalf of a page admin is a bit more tricky.

Getting access token would require asking one of page admins for manage_pages extra permissions and then retrieving their "accounts" information that would contain access_token fields for every page where they have admin permissions (this process is described here in "Impersonation" section).

To achieve this we would need to run following script at our app's "Connect URL" location and ask one of page admins to go through our permission request process during facebook login:

//API init code is omitted
FB.login(function() {
    FB.api('/me/accounts', 'get', {}, function(response) {
        console.log(response);
    });
}, {perms:'publish_stream,offline_access,manage_pages'});

Now if you have Firebug console installed you should be able to explore returned response and find access_token fields for all fan pages current user is managing.

To make a Fan Page wall post all we need now is to send similar HTTP POST request as before to:

https://graph.facebook.com/me/feed

(note that it is pointing now to me/feed, not <PAGE_ID>/feed) and pass just retrieved access token among other wall post parameters:

//API init code is omitted
var wallPost = {
    access_token: "<ACCESS_TOKEN>",
    message: 'Hello, World!'
};

FB.api('/me/feed', 'post', wallPost, function(response) {
    if (!response || response.error) {
        alert('Error occurred');
    } else {
        alert('Success!');
    }
});

Corresponding Java API:

FacebookClient facebookClient = new DefaultFacebookClient("<ACCESS_TOKEN>");
FacebookType publishMessageResponse = facebookClient.publish("me/feed", FacebookType.class, Parameter.with("message", "Hello, World!"));
Jul 28, 2010

How to Make Java MD5 Match PHP and MySQL MD5

JavaMySQLPHP

Here is Java implementation of MD5 hashing that will produce exactly the same result as md5() function in PHP and MySQL:

public static String md5(String input) throws NoSuchAlgorithmException {
    String result = input;
    if(input != null) {
        MessageDigest md = MessageDigest.getInstance("MD5"); //or "SHA-1"
        md.update(input.getBytes());
        BigInteger hash = new BigInteger(1, md.digest());
        result = hash.toString(16);
        while(result.length() < 32) { //40 for SHA-1
            result = "0" + result;
        }
    }
    return result;
}
Feb 11, 2010

Creating Custom Directives for Apache Velocity

JavaVelocity

In this article we will go over Apache Velocity API and write our own inline and block directives. Like in the previous article about creating Velocity tools, we will be trying to duplicate the behavior of truncate() method from DisplayTools which truncates a long string.

There are two directive types in Velocity - inline and block directives. Inline directives consist from a single line, while block directives have body and closing tag #end:

#include( "one.gif","two.txt","three.htm" )
#if( $foo )
   Block directive...
#end

Creating inline directive

To declare your custom directive you need to specify userdirective parameter in velocity config (for example in velocity.properties file) and provide comma separated list of complete user directive class names:

userdirective=com.example.MyDirective1, com.example.MyDirective2

Lets create our first inline directive #truncate with 4 parameters:

#truncate(Object truncateMe, int maxLength, String suffix, boolean truncateAtWord) 

First parameter contains a string we want to truncate and is the only required parameter, the other 3 set optional truncating settings.

Lets look at the final directive code and then go over it in details:

package ca.sergiy.velocity;

import java.io.IOException;
import java.io.Writer;

import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.parser.node.Node;

public class TruncateDirective extends Directive {

    public String getName() {
        return "truncate";
    }

    public int getType() {
        return LINE;
    }

    public boolean render(InternalContextAdapter context, Writer writer, Node node) 
            throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {

        //setting default params
        String truncateMe = null;
        int maxLength = 10;
        String suffix = null;
        Boolean truncateAtWord = false;

        //reading params
        if (node.jjtGetChild(0) != null) {
            truncateMe = String.valueOf(node.jjtGetChild(0).value(context));
        }

        if (node.jjtGetChild(1) != null) {
            maxLength = (Integer)node.jjtGetChild(1).value(context);
        }

        if (node.jjtGetChild(2) != null) {
            suffix = String.valueOf(node.jjtGetChild(2).value(context));
        }

        if (node.jjtGetChild(3) != null) {
            truncateAtWord = (Boolean)node.jjtGetChild(3).value(context);
        }

        //truncate and write result to writer
        writer.write(truncate(truncateMe, maxLength, suffix, truncateAtWord));

        return true;

    }

    //does actual truncating (taken directly from DisplayTools)
    public String truncate(String truncateMe, int maxLength, String suffix, boolean truncateAtWord) {
        if (truncateMe == null || maxLength <= 0) {
            return null;
        }

        if (truncateMe.length() <= maxLength) {
            return truncateMe;
        }
        if (suffix == null || maxLength - suffix.length() <= 0) {
            // either no need or no room for suffix
            return truncateMe.substring(0, maxLength);
        }
        if (truncateAtWord) {
            // find the latest space within maxLength
            int lastSpace = truncateMe.substring(0, maxLength - suffix.length() + 1).lastIndexOf(" ");
            if (lastSpace > suffix.length()) {
                return truncateMe.substring(0, lastSpace) + suffix;
            }
        }
        // truncate to exact character and append suffix
        return truncateMe.substring(0, maxLength - suffix.length()) + suffix;

    }

}

All directives in Apache extend Directive class that has 3 abstract methods we need to implement: getName(), getType() and render().

First method getName() should return a name of your directive that will be used in templates.

Method getType() returns BLOCK or LINE constants which determine a directive type.

Third method render(InternalContextAdapter context, Writer writer, Node node) is where all the work is happening. Writer is our template writer where we are going to write the result. Node object contains information about our directive (its parameters and properties) and InternalContextAdapter contains everything Velocity needs to know about the template in order to render it.

Directive parameters (or, more precisely, nodes, representing parameters) can be accessed from directive node by calling node.jjtGetChild(i), where i is parameter number (starting from zero). You can get total number of parameters using node.jjtGetNumChildren().

jjtGetChild(i) method returns Node object, from which you can either get "rendered" value by calling node.jjtGetChild(i).value() method, or retrieve its literal value by calling node.jjtGetChild(i).literal(). To demonstrate the difference between rendered and literal values lets take a look at this template:

#set($test = "Test Value")
#truncate("This is $test")

(String)node.jjtGetChild(0).value() will return "This is Test Value", while (String)node.jjtGetChild(0).literal() will return "This is $test". In most cases you will need rendered values.

Lets test our newly created directive. First we need to declare it in config file so Velocity could find it:

userdirective=ca.sergiy.velocity.TruncateDirective

Now lets run this template:

#set($test = "long line that should be truncated")
#truncate("Testing $test", 20, "...", true)

Result:

Testing long line... 

Creating block directive

Now lets turn our inline directive into block directive:

#truncateBlock(int maxLength, String suffix, boolean truncateAtWord) 
    Long block 
    that will be 
    truncated
#end

It will work the same as before with all parameters being optional. Lets look at the code:

package ca.sergiy.velocity;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.parser.node.Node;

public class TruncateBlockDirective extends Directive {

    private Log log;

    private int maxLength;
    private String suffix;
    private Boolean truncateAtWord;

    public String getName() {
        return "truncateBlock";
    }

    public int getType() {
        return BLOCK;
    }

    @Override
    public void init(RuntimeServices rs, InternalContextAdapter context, Node node) throws TemplateInitException {
        super.init(rs, context, node);
        log = rs.getLog();

        //read dafault values from config
        maxLength = rs.getInt("userdirective.truncateBlock.maxLength", 10);
        suffix = rs.getString("userdirective.truncateBlock.suffix", "...");
        truncateAtWord = rs.getBoolean("userdirective.truncateBlock.truncateAtWord", false);

    }

    public boolean render(InternalContextAdapter context, Writer writer, Node node) 
            throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {

        log.debug("truncateBlock directive render() call");

        String truncateMe = null;

        //default settings
        int maxLength = this.maxLength;
        String suffix = this.suffix;
        Boolean truncateAtWord = this.truncateAtWord;

        //loop through all "params"
        for(int i=0; i<node.jjtGetNumChildren(); i++) {
            if (node.jjtGetChild(i) != null ) {
                if(!(node.jjtGetChild(i) instanceof ASTBlock)) {
                    //reading and casting inline parameters
                    if(i == 0) {
                        maxLength = (Integer)node.jjtGetChild(i).value(context);
                    } else if(i == 1) {
                        suffix = String.valueOf(node.jjtGetChild(i).value(context));
                    } else if(i == 2) {
                        truncateAtWord = (Boolean)node.jjtGetChild(i).value(context);
                    } else {
                        break;
                    }
                } else {
                    //reading block content and rendering it  
                    StringWriter blockContent = new StringWriter();
                    node.jjtGetChild(i).render(context, blockContent);

                    truncateMe = blockContent.toString();
                    break;
                }
            }
        }

        //truncate and write result to writer
        try {
            writer.write(truncate(truncateMe, maxLength, suffix, truncateAtWord));
        } catch (Exception e) {
            String msg = "Truncate failed";
            log.error(msg, e);
            throw new RuntimeException(msg, e);

        }
        return true;

    }

    //does actual truncating (taken directly from DisplayTools)
    public String truncate(String truncateMe, int maxLength, String suffix,
            boolean truncateAtWord) {
        if (truncateMe == null || maxLength <= 0) {
            return null;
        }

        if (truncateMe.length() <= maxLength) {
            return truncateMe;
        }
        if (suffix == null || maxLength - suffix.length() <= 0) {
            // either no need or no room for suffix
            return truncateMe.substring(0, maxLength);
        }
        if (truncateAtWord) {
            // find the latest space within maxLength
            int lastSpace = truncateMe.substring(0, maxLength - suffix.length() + 1).lastIndexOf(" ");
            if (lastSpace > suffix.length()) {
                return truncateMe.substring(0, lastSpace) + suffix;
            }
        }
        // truncate to exact character and append suffix
        return truncateMe.substring(0, maxLength - suffix.length()) + suffix;

    }

}

First difference is that we additionally overrode init() method from Directive class that allows us to get access to RuntimeServices object in order to get logger instance and read default directive parameters from configuration file. So now our velocity.properties file looks like this:

userdirective=ca.sergiy.velocity.TruncateDirective, ca.sergiy.velocity.TruncateBlockDirective
userdirective.truncateBlock.maxLength=10
userdirective.truncateBlock.suffix=...
userdirective.truncateBlock.truncateAtWord=false

Setting all default parameter values in config is optional, because if parameter is not present we will still assign hardcoded default value. For example this line sets maxLength to userdirective.truncateBlock.maxLength value if present, otherwise it is set to 10:

maxLength = rs.getInt("userdirective.truncateBlock.maxLength", 10);

There is no official recommendations for config parameter naming for user directives, but using userdirective.{directive}.{param} format should be good choice.

render() method looks a little different this time as well. The reason for this is that block content is passed as node's last child (after other inline parameters), but because all our parameters are optional we need to somehow separate block content from other inline parameters. One way of doing this is to check chlid node's class name. For block content it is always ASTBlock, which is different from the rest. In order to render ASTBlock value we have to use render(InternalContextAdapter context, Writer writer) method that will render it to a provided writer (as opposing to simply calling node.value() for inline parameters).

Conclusion

In this article we looked at creating custom user directives for Apache Velocity. But before writing your own Velocity directive you should consider using tools approach instead if possible, as writing custom directives should be reserved for extending core template language or working with multiline parameters. For example if "truncate" method was not present in DispalyTools already, proper way of doing it would be creating new Tool class and adding it there instead of making it a directive.

Creating custom directives is not too complicated, but they are not well documented and it is hard to find any info or tutorials about them. If you need more examples take a look at Velocity built-in directive sources. There are some simple directive examples on VelocityTools wiki. You can also check out my open-source htmlcompressor project that has some block directives.

Sep 9, 2009
profile for serg at Stack Overflow, Q&A for professional and enthusiast programmers