How to implement OAuth Facebook security in the server with Java

Well, things change, improve and makes your life more difficult. Now we have a deadline from FB in October first, all the people should use OAuth authentication, in other words: the FB security changes.

The FB documentation is accurate, but with examples in PHP. The main changes are: now the cookie is encrypted, the content changes and it doesn’t have the security access token in it, you need to call https://graph.facebook.com/oauth/access_token to get it in a REST fashion. In our GWT page we get the security cookie in the server from the browser, get the “code” part of it and use it to obtain an access token. So that is the scenario that the code supports, no other.

Here is how we have implemented that in our site:


// http://developers.facebook.com/docs/authentication/
// http://developers.facebook.com/docs/authentication/signed_request/
// http://blog.sociablelabs.com/2011/09/19/server-side-changes-facebook-oauth-2-0-upgrade/
public class FaceBookSecurity {

	// return the fb user in the cookie.
	public static String getFBUserFromCookie(HttpServletRequest request)
			throws Exception {
		Cookie fbCookie = getFBCookie(request);

		if (fbCookie == null)
			return null;

		// gets cookie value
		String fbCookieValue = fbCookie.getValue();

		// splits it.
		String[] stringArgs = fbCookieValue.split("\\.");
		String encodedPayload = stringArgs[1];

		String payload = base64UrlDecode(encodedPayload);

		// gets the js object from the cookie
		JsonObject data = new JsonObject(payload);

		return data.getString("user_id");

	}

	public static boolean ValidateFBCookie(HttpServletRequest request)
			throws Exception {

		Cookie fbCookie = getFBCookie(request);

		if (fbCookie == null)
			throw new NotLoggedInFacebookException();

		// gets cookie information
		String fbCookieValue = fbCookie.getValue();

		String[] stringArgs = fbCookieValue.split("\\.");
		String encodedSignature = stringArgs[0];
		String encodedPayload = stringArgs[1];

		//decode
		String sig = base64UrlDecode(encodedSignature);
		String payload = base64UrlDecode(encodedPayload);

		// gets the js object from the cookie
		JsonObject data = new JsonObject(payload);

		if (!data.getString("algorithm").equals("HMAC-SHA256")) {
			return false;
		}

		SecretKey key = new SecretKeySpec(
				ApplicationServerConstants.FacebookSecretKey.getBytes(),
				"hmacSHA256");

		Mac hmacSha256 = Mac.getInstance("hmacSHA256");
		hmacSha256.init(key);
		// decode the info.
		byte[] mac = hmacSha256.doFinal(encodedPayload.getBytes());

		String expectedSig = new String(mac);

		// compare if the spected sig is the same than in the cookie.
		return expectedSig.equals(sig);

	}

	public static String getFBAccessToken(HttpServletRequest request)
			throws Exception {
		Cookie fbCookie = getFBCookie(request);

		String fbCookieValue = fbCookie.getValue();

		String[] stringArgs = fbCookieValue.split("\\.");
		String encodedPayload = stringArgs[1];

		String payload = base64UrlDecode(encodedPayload);

		// gets the js object from the cookie
		JsonObject data = new JsonObject(payload);

		String authUrl = getAuthURL(data.getString("code"));
		URL url = new URL(authUrl);
		URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(),
				url.getQuery(), null);
		String result = readURL(uri.toURL());

		String[] resultSplited = result.split("&");

		return resultSplited[0].split("=")[1];

	}

	// creates the url for calling to oauth.
	public static String getAuthURL(String authCode) {
		String url = "https://graph.facebook.com/oauth/access_token?client_id="
				+ ApplicationConstants.FacebookApiKey
				+ "&redirect_uri=&client_secret="
				+ ApplicationServerConstants.FacebookSecretKey + "&code="
				+ authCode;

		return url;
	}

	// reads the url.
	private static String readURL(URL url) throws IOException {

		InputStream is = url.openStream();

		InputStreamReader inStreamReader = new InputStreamReader(is);
		BufferedReader reader = new BufferedReader(inStreamReader);

		String s = "";

		int r;
		while ((r = is.read()) != -1) {
			s = reader.readLine();
		}

		reader.close();
		return s;
	}

	private static String base64UrlDecode(String input) {
		String result = null;
		Base64 decoder = new Base64(true);
		byte[] decodedBytes = decoder.decode(input);
		result = new String(decodedBytes);
		return result;
	}

	private static Cookie getFBCookie(HttpServletRequest request) {
		Cookie[] cookies = request.getCookies();

		if (cookies == null)
			return null;

		Cookie fbCookie = null;

		for (Cookie c : cookies) {
			if (c.getName().equals(
					"fbsr_" + ApplicationServerConstants.FacebookApiKey)) {
				fbCookie = c;
			}
		}

		return fbCookie;
	}

}
About these ads

13 comments

  1. This is helpful, thanks. But could you add your usings to the code? A few objects you instantiate do not exist in the .NET library (JsonObject, SecretKey, Mac)

    1. It is java code, sorry.

  2. Ricardo Carvalho · · Reply

    Can you please share the classes packages (e.g. Base64) ?

    1. Here you have them:

      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.net.URI;
      import java.net.URL;

      import javax.crypto.Mac;
      import javax.crypto.SecretKey;
      import javax.crypto.spec.SecretKeySpec;
      import javax.servlet.http.Cookie;
      import javax.servlet.http.HttpServletRequest;

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

      import com.restfb.json.JsonObject;

  3. Pablo Castilla :
    It is java code, sorry.

    Indeed, my error. Your post was still very helpful though, so thanks!

  4. Abhishek Mishra · · Reply

    would you please Specify import file and jar for “ApplicationServerConstants”

    1. It is a general class with constant values, just that. You can create your own one.

  5. Thanks very much for this code, I found it very useful. I did notice one minor bug though, in line 52-54 you have.

    if (data.getString(“algorithm”) != “HMAC-SHA256″) {
    return false;
    }

    This should probably be:

    if (!data.getString(“algorithm”).equals(“HMAC-SHA256″)) {
    return false;
    }

    1. Ouch! Thanks! I use c# at work and java at home… sometimes it is difficult not to mix them :)

  6. Hi Pablo,

    Thanks for this post in your blog. I am strugling for a week to authenticate a java desktop app in facebook. I am hoping this wil do. I have managed to run your example, but can you please tell me what is the argument “request” (an example please) in the “public static String getFBAccessToken(HttpServletRequest request)” method?

    Thank you

  7. Hi Pablo,

    Many thanks for this.

    Can you please tell me what the “request” argument is (example please) on public static String getFBAccessToken(HttpServletRequest request) metod?

    Thank you.

    1. it is the servlet request. I need that to get the facebook’s cookie.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 138 other followers

%d bloggers like this: