Páginas

2011-09-23

[drops.log]: Facebook in Java

I started to do a small piece of code to interact with facebook. I spent some days wondering why the hell my application wasn’t working. The case is bad documentation on Facebook developers page. They say you should pass the authorization code and your app secret to the Graph API token endpoint from an URL like this:

https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
That is very misleading since the URL contains a parameter redirect_uri that is useless. They never redirect from your page. You get the access_token not as parameter but as content.
I tested facebook-api and restfb and none made a good work to authenticate. You must lead with the initical oauth/dialog url, get and parse the content to retrive the access_token. There is a reasonable elegant way to do that on java servlets: create a Filter that redirect to the authorization page and retrieves the content on the URL above.
Here is the code:
public class FacebookAuthorizationFilter implements Filter {
    public static final String AUTH_URL_BASE = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s&scope=%s";
    public static final String ACCESS_TOKEN_URL_BASE = "https://graph.facebook.com/oauth/access_token?client_id=%s&redirect_uri=%s&client_secret=%s&code=%s";
    
    @Override
    public void init(FilterConfig filterConfig) 
        throws ServletException 
    { /*init code*/ }
    
    @Override
    public void destroy() { /*destroy code*/ }

    @Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		
		String code = request.getParameter(MyConstants.CODE_PARAM_KEY);
		String accessToken = request.getParameter(MyConstants.ACCESS_TOKEN_PARAM_KEY);
		
		if(accessToken != null) {
			//there is one already let the application follow usually
			chain.doFilter(req, resp);
			return;
		}else {
			if(code == null) {
				//ask for authorization from facebook
				resp.sendRedirect(String.format(AUTH_URL_BASE, this.appKey, this.redirectURI, this.scope));
			} else {
				//already authorized, fetch the access_code
				accessToken = retriveAccessToken(code);
				//save the access_token
				req.getSession().setAttribute(MyConstants.ACCESS_TOKEN_KEY, accessToken);
				chain.doFilter(req, resp);
			}
		}
	}

    /**
     * Retrieves the content of the page with the access_code token 
     * @param code the code parameter passed to the redirect_uri from the oath/dialog page
     * @return The String access_token provided from facebook
     * @throws IOException
     */
    private String retriveAccessToken(String code) throws IOException {
	    URL url = new URL(String.format(ACCESS_TOKEN_URL_BASE, this.appKey, this.redirectURI, this.appSecret, code));
	    //TODO: Try to sintetize with CharStreams from guava-libraries 
	    ByteArrayOutputStream byteWriter = new ByteArrayOutputStream();
	    InputStream urlReader = url.openStream();
	    int r;
	    while((r = urlReader.read()) != -1) {
		    byteWriter.write(r);
	    }
	    String fetchedResult = new String(byteWriter.toByteArray());
	
	    String[] fields = fetchedResult.split("&");
	    //TODO: Make a regex to extract that
	    for(String field : fields) {
		    String[] pair = field.split("=");
		    if(pair.length == 2 && pair[0].equals("access_token")) {
			    return pair[1];
		    }
	    }
	    throw new RuntimeException(fetchedResult);
    }
}
Sumaring the process on a servlet:
  1. Make a filter that asks for the client object or access_token on the session objectc
  2. If there is no parameter in the session stops the request and redirect to:
  3. https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&scope=email,read_stream
    1. Where scope are the permissions you want
    2. The redirect_uri is going to work this case and you must to have a filter in the redirect_uri able to get the code parameter (line 30 in the snippet above)
  4. Fetch the content of the follow URL:
  5. https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
    1. (Lines 48 to 56 in the snippet above)
  6. Parse the content to extract the parameters (at least acess_token) (lines 60 to 65 on the snippet above)
  7. With access_token you can create the actual client object to access facebook data (line 37 on the snippet above)
  8. Put the object where you can retrieve it (line 35 in the snippet above)

[+/-] show/hide