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