Friday, December 17, 2010

Facebook apps on App Engine without any third party API



12/17/2010 UPDATE


A lot of people found this blog posting because as we go through the authorization process, our users end up getting a "go to facebook.com" logo presented to their users when they first give their users a link to click on to authorize. This is happening because you need to put a target="_top" in the link.  I kid you not, if the user clicks on a link in your welcome page and they get a "Go to Facebook.com" message before they can go to the authorization screen, it's because you need to set target=_top in the link.  




It was a frustrating week or two while I tried to integrate Facebook into the google app engine. My goal was to use App Engine as my back end and Facebook as my front end for my apps.  I wanted users to add my app to their Facebook profile and grab a non-expiring oauth token for that user so I could access their Facebook data even when they were not using my app.  


The most important thing to know about doing this today is that any of  the third party apis like FaceBook4J jars are all completely useless here. Facebook exposes everything about a user using the graph api that can be accessed using http requests and getting json data back.  Trying to use these with app engine and the Google web toolkit will end up having you run into problems with libraries not being supported on either the server side or gwt client.  You don't need em - understanding oauth and urlfetch on app engine is all you need.


This blog posting covers the process of loading up your app in a Facebook canvas, getting the OAUTH token for your user, and then interacting with the Facebook graph API to get JSON data back from facebook for your user. All without any third party apis, SDK or anything else. I'll take you all the way to getting the current user's email. 


The only major assumption i'm making on this posting is that you are someone who is developing Java App Engine programs in the same way as the Google Stock Watcher tutorial 
http://code.google.com/webtoolkit/doc/1.6/tutorial/gettingstarted.html


So, you are familiar with the client / server namespaces and calling servlets from client interfaces to server side implementations. 

The first thing you need to do is set up a developer account and APP and on Facebook. Go to http://www.facebook.com/developers/ and set up a new application. You're going to need your app secret, api key and app ID.


Now, take the time to read over this manual (SERIOUSLY!) http://developers.facebook.com/docs/api it explains the Facebook standards for the graph api


Then go to app engine and create your corresponding app engine app. https://appengine.google.com/


I'm going to assume you also have eclipse IDE with the app engine add on and are able to create a new web application project and deploy it. 


Just for fun, I made an app on Facebook called Lucid Bubble, it's a simple dream journaling program that lets you record your dreams, tag your friends who are in the dream and post to the news feed about it. Not an app i would use every day but there seemed to be a demand for it. It also used app engine's mail hander so you can email a dream using your phone at bedside and post that way. 


Here is a screen shot of the end result: A nice app running inside a facebook iframe and using GWT, Google Charts and Facebook graph api data. The url for this app is http://apps.facebook.com/lucidbubble and it's being pulled from http://lucidbubble.appspot.com






Facebook App Settings:

Back on the Facebook app setting page. You can temporarily set the canvas URL to http://127.0.0.1:8888/. Later, you'll set that to your your deployed url on appspot.

Code:

I'm going to walk through the code that is first executed on the app engine entry point - OnModuleLoad() and the process of getting a code parameter, passing that code to the server side, doing oauth to get a user token and then requesting from facebook user data. I'm going to stop abruptly there because at that point you have everything you need to take to facebook from app engine.

When Facebook loads your app for the first time, it is the raw URL http://apps.facebook.com/yourapp. The first challenge is getting the code url parameter. 

Annoying Undocumented Facebook Behavior #1:

What is extremely annoying about this is that the Facebook docs tell you that to get the code you need to "Redirect the user to the authorization URL" but you can't redirect users in the iframe! If you do, you get an annoying and uninformative Facebook error page that says: "Go to Facebook.com" and when you click it, you actually go on to the authorization URL.  You also need to make sure you set the target=_top in your link

Ok, enough ranting, here is the client code: To break this down,  onModuleLoad() checks to see if the code is in the url parameter. If it is, we send the code to the server side for oauth processing (posted further down). If we do not have the code, we need to present the user with a welcome screen that has a link they can click on to go to add the app or authorize it. You can't just redirect the user to that url which would be nice.

private void loadWelcome()
{
//3: build a welcome page with a link to the authorise url
 
ContentPanel panel = PanelLibrary.VPanel();
viewport = new Viewport();
viewport.setLayout(new BorderLayout());
viewport.setBorders(true);
Anchor h = new Anchor();
h.setText("Click here to open your dream journal");
h.setHref(Facebook.getLoginRedirectURL());
h.setTarget("_top");
h.setStyleName("main-anchor");
panel.add(h, new RowData(-1, -1, new Margins(5,10,0,0)));  
viewport.add(panel);
RootPanel.get("main").add(viewport);
}

public void onModuleLoad() {

//1: do we have a code in the url?

final FaceBookServiceAsync facebookService = GWT.create(FaceBookService.class);
String  code = Location.getParameter("code");
if ( code == null)
{
//2: no, we need to bounce off the authorise url
loadWelcome();
}
else
{
//4: we must be back from the authorizes url, we have a code in the url - pass the code to the servlet:

facebookService.facebookLogin( code,Facebook.getClientId(), Facebook.getLoginRedirectURL(), Facebook.getSecret()  ,  new AsyncCallback(){
@Override
public void onFailure(Throwable caught) {
//loadWelcome();
Window.alert(caught.getMessage());
}
@Override
public void onSuccess(String email) {
//Window.alert(email);
loadLayout(email.toLowerCase());
}
private void loadLayout(String email) {
//5: all is well, load my app UI
}
});
}
}



LoadLayout is of course where your app starts to build the user interface, now that you have access to the users graph.

Here is the class I use for getting my api key, secrets and authorisation url, which is the first step to doing oauth.  Remember:  The redirect_URI must end with a /  and match the uri in your Facebook connect setting on the facebook app setting page. Also,

Annoying Undocumented Facebook Behavior #2

The redirect URI has a scope parameter that specifies what permisions your app needs, If you change these, Facebook may return an oauth token later in an unexpected format. I recomend you keep them the way I post them here and tweak them after you get everything working. Change anything below at your own risk, pay attention to trailing / and other params that will break your app. Make sure redirect_uri matches your app settings.


public class Facebook {
    // get these from your FB Dev App
    private static final String api_key = "1088xxxxxxx774d5ce9";
    private static final String secret = "secretkeyhere";
    private static final String client_id = "1530xx000432613";  
    public static String getClientId() {
return client_id;
}
    private static final String redirect_uri = "http://apps.facebook.com/lucidbubble/";
    public static String getAPIKey() {
        return api_key;
    }
    public static String getSecret() {
        return secret;
    }
    public static String getLoginRedirectURL() {
        return "https://graph.facebook.com/oauth/authorize?client_id=" +
            client_id + "&display=page&redirect_uri=" +
            redirect_uri+"&scope=user_status,publish_stream,offline_access,email";
    }
    public static String getAuthURL(String authCode) {
        return "https://graph.facebook.com/oauth/access_token?canvas=1&fbconnect=0&client_id=" +
            client_id+"&redirect_uri=" +
            redirect_uri+"&client_secret="+secret+"&code="+authCode;
    }
}


So far:

1. Your app should load and preset the user with a link to the authorization URL.
2. They'll then be sent back to the same page
3. OnModuleLoad will fire again but this time we'll have the code.

Everything else happens on the server all the way up to storing the users permanent OAUTH token and getting their Facebook info. This is all happening in my FacebookServiceImpl.java class that is called asynchronously from the module above. I'm assuming you know how to do this because you have gone through the app engine stock watcher tutorial :-)

 Get The Oauth Token.

With the code in hand, this is all you need to do to get the oauth token for a user on the server:


public String getToken(String code, String ClientID, String redirectURL, String secret)
{
String encodedCode="";
String retStr;
try {
encodedCode = URLEncoder.encode(code, "UTF-8");
String u1 = "https://graph.facebook.com/oauth/access_token?" +
"client_id=" +  ClientID + "&" +
"redirect_uri=" + redirectURL + "&" +  
"client_secret=" + secret + "&" +
"type=user_agent&" +
"code=" + encodedCode;
retStr = fetchURL(u1);
} catch (UnsupportedEncodingException e) {
retStr = null;
}
return retStr;



}


Pass to this function your client id, the same redirect url and secret stored in the Facebook class above. The exact same parameters you used in the client code to get the code. This function is using a generic function I wrote for taking any url, posting it and getting the result. Here is that function fetchURL:


private String fetchURL(String u) 
{
String retStr = "";
try 
{
           URL url = new URL(u);    
           BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
           String line;
                while ((line = reader.readLine()) != null) {
            retStr += line;
           
           }
           reader.close();


       } catch (MalformedURLException e) {
           // ...
       } catch (IOException e) {
           // ...
       }
return retStr;

}



Annoying Undocumented Facebook Behavior #3

Facebook does not return the plain oauth token, rather it returns the token like this: access_token=jhjvk... with the oauth token after the equals sign. If you want to use the token, you have to make sure it is url encoded. You'll see in my code here a simple function to use the token in this format, but removing the access_token= part, encoding  it and then putting it back together.


private String urlEncodeToken(String unencodedToken)
{
  String[] s =unencodedToken.split("=");
String retStr = null;
try {
retStr = "access_token=" + URLEncoder.encode(s[1], "UTF-8");
} catch (UnsupportedEncodingException e) {
}
return retStr;

}



Here is the complete facebook login function being called from the client. You'll see i'm

1. Using the code to get the token (above)
2. Using the token to get the email from https://graph.facebook.com/me  (me represents the authorized user, so that's the URL you use to get the users currently on your app)
3. Downloading the JSON data using my fetchURL functon (above)

At this point you're good to go, you have the users data and can interact with the Facebook graph. I also:

4. Convert the JSON into an object using GSON (google json api). My FBUser class is just a simple class with properties that match the json (email, name strings with getters and setters)  and a no-param constructor)


public String facebookLogin( String code, String ClientID, String redirectURL, String secret) 
{
                 String meURL =   "https://graph.facebook.com/me";
String token = getToken(  code,   ClientID,   redirectURL,   secret);
String email = "";
   String jsonEmail = fetchURL(meURL + "?" +  urlEncodeToken(token)  + "&fields=email,name");
FBUser fbuser = gson.fromJson(jsonEmail, FBUser.class);
email = fbuser.getEmail();
  return email;

}



You can now post and get from any of the url's documented in the graph api docs. http://developers.facebook.com/docs/api 

19 comments:

  1. This is great information. I appreciate the sincere appreciation. Thank you...


    network

    ReplyDelete
  2. Finally, Java based tutorial for Facebook developers.

    Thanks...

    ReplyDelete
  3. would it be possible for to post the details of your web.xml file here?

    ReplyDelete
  4. THIS WORKED!! GODAMMIT!
    Splitting my hair on the stupid facebook site for one week when it took me half the time to write a scalable spring app using bigtable and deploy to appengine.
    Fuck!

    Thanks a lot mate. I owe you a beer.

    ReplyDelete
  5. Could you provide the full example?
    I don't understand how you managed to resolve the first problem:
    Annoying Undocumented Facebook Behavior #1.

    ReplyDelete
  6. Many thanks - I've been banging my head against dead apis and obfuscated fb documentation for too long.

    One contribution - if you change your urlEcodeToken to

    ...split("[=&]");

    then it makes allowance for the "expires" parameter than comes back under some calls. The later reference to s[1] still works unchanged.

    Thanks.

    ReplyDelete
  7. As a further update, I was trying to make the first 'authenticate/authorize' page redirect nicer; my redirect shows a bare facebook banner/login with a big "Go to facebook" button that has a convoluted url behind it.

    Didn't manage to crack that, but in the process, facebook has stopped sending back a "code" parameter and instead is sending back a fb_sig_session_key. I can exchange this for an access token as per fb doc, but haven't figured out (a) why fb won't send back "code" and (b) how to get a 'nice' page to show for the first 'authenticate/authorize' page.

    I'll blog my escapades and put a link back in.

    ReplyDelete
  8. Love the comments guys - thanks! I'm going to incorporate some of the feedback i'm getting into an open source FB client and post soon. I am getting some new behavior from FB recently with this code in which sometimes URL encoding the token breaks it while other times you have to encode it. I'll keep you posted

    ReplyDelete
  9. Thanks alot dude.... you saved my life..... :D

    ReplyDelete
  10. Thank you so much! Really looking forward to seeing the full source code.

    ReplyDelete
  11. so you're saying it's impossible to automatically redirect the user? Q.Q

    I've been stuck on that part for ages and finally gave up...

    I'm still not clear about how to resolve the issue :/

    I'm using RestFB (http://restfb.com) and it seems to be better than facebook-java-api

    Thanks for the great post.

    ReplyDelete
  12. Hello,

    Here is an example of a Facebook app built using GWT/GAE. We had to move a lot of the Facebook call to the client side using Facebook Graph Api because of timeout issues calling Facebook from Appengine. Check this out http://apps.facebook.com/pageiframetab

    ReplyDelete
  13. This is really great and awesome but amazing informational work.

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. where you able to use http://localhost:8888/?gwt.codesvr=localhost:9997 as the redirect URI, to be able to continue and debug the client side?

    ReplyDelete
  17. Can you share some of the advance feature of facebook and how you use them on the client side? for example how can you show a sample on the client side to get the friends.getAppUsers?

    Thanks.

    ReplyDelete
  18. I'm sorry i wasn't noticing the comments on this blog post - if you're interested in this, go to my website www.nimbits.com and their is a link to the source on github which is a project I maintain that interacts with facebook from app engine, there you'll fine the maintained code on how I talk to facebook. You can also post your questions on the form from nimbits.com - thanks!

    ReplyDelete