Facebook Canvas App Authentication – Java

Facebook will stop supporting canvas FBML apps very soon. So this article only talks about iFrame apps build in Java and using OAuth 2.0 protocol. The basic flow of authentication procedure can be found at facebook’s developer section.

When user is logged in Facebook and access to your app, it sends a signed request to the canvas page of the app using POST method. So in your servlet you have to handle the request in doPost method. Check for the presence of Oauth token in the signed request. If that is not present ask for it.

The signed request is a base64url encoded Json object. Now I couldn’t find anywhere a mapping class which can be mapped to the Json object after base64url decoded signed request. So I had to write it by myself.

public class FacebookSignedRequest {

	private String algorithm;
	private Long expires;
	private Long issued_at;
	private String oauth_token;
	private Long user_id;
	private FacebookSignedRequestUser user;
	
	public String getAlgorithm() {
		return algorithm;
	}
	
	public void setAlgorithm(String algorithm) {
		this.algorithm = algorithm;
	}
	
	public Long getExpires() {
		return expires;
	}
	
	public void setExpires(Long expires) {
		this.expires = expires;
	}
	
	public Long getIssued_at() {
		return issued_at;
	}
	
	public void setIssued_at(Long issued_at) {
		this.issued_at = issued_at;
	}
	
	public String getOauth_token() {
		return oauth_token;
	}
	
	public void setOauth_token(String oauth_token) {
		this.oauth_token = oauth_token;
	}
	
	public Long getUser_id() {
		return user_id;
	}
	
	public void setUser_id(Long user_id) {
		this.user_id = user_id;
	}

	public FacebookSignedRequestUser getUser() {
		return user;
	}

	public void setUser(FacebookSignedRequestUser user) {
		this.user = user;
	}
	
	public static class FacebookSignedRequestUser {

		private String country;
		private String locale;
		private FacebookSignedRequestUserAge age;
		
		public String getCountry() {
			return country;
		}

		public void setCountry(String country) {
			this.country = country;
		}

		public String getLocale() {
			return locale;
		}

		public void setLocale(String locale) {
			this.locale = locale;
		}

		public FacebookSignedRequestUserAge getAge() {
			return age;
		}

		public void setAge(FacebookSignedRequestUserAge age) {
			this.age = age;
		}

		public static class FacebookSignedRequestUserAge{
			private int min;
			private int max;

			public int getMin() {
				return min;
			}

			public void setMin(int min) {
				this.min = min;
			}

			public int getMax() {
				return max;
			}

			public void setMax(int max) {
				this.max = max;
			}
		}
	}
}

Now I write a Facebook util class which works as a configuration set up for Facebook app

public class FacebookAuthService {

	private static final String apiKey = "APP_KEY";
	private static final String appSecret = "APP_SECRET";
	private static final String appId = "APP_ID";
	
	private static final String redirect_uri = "https://apps.facebook.com/YOUR_APP_PATH";
	
	private static final String[] perms = new String[] {"publish_stream", "email"};
	
	public static String getAPIKey() {
		return apiKey;
	}

	public static String getSecret() {
		return appSecret;
	}

	public static String getLoginRedirectURL() {
		return "https://graph.facebook.com/oauth/authorize?client_id=" + appId
				+ "&display=page&redirect_uri=" + redirect_uri + "&scope="
				+ StringUtils.join(perms);
	}

	public static String getAuthURL(String authCode) {
		return "https://graph.facebook.com/oauth/access_token?client_id="
				+ appId + "&redirect_uri=" + redirect_uri + "&client_secret="
				+ appSecret + "&code=" + authCode;
	}
	
	public static String getAuthURL() {
		return "https://www.facebook.com/dialog/oauth?client_id="
				+ appId + "&redirect_uri=" + redirect_uri + "&scope="
				+ StringUtils.join(perms);
	}
	
	public static FacebookSignedRequest getFacebookSignedRequest(String signedRequest) throws Exception{
		
		String payLoad = signedRequest.split("[.]", 2)[1];
		payLoad = payLoad.replace("-", "+").replace("_", "/").trim();
		
		String jsonString = new String(Base64.decodeBase64(payLoad));
		return new ObjectMapper().readValue(jsonString, FacebookSignedRequest.class);
	}
}

I’ve used Jackson to map Json to Object here. But any other API can be used too.

Now in my entry servlet (my canvas URL) I check for it in the doPost method like

String signedRequest = (String) request.getParameter("signed_request");

FacebookSignedRequest facebookSignedRequest = FacebookAuthService.getFacebookSignedRequest(signedRequest);
PrintWriter writer = response.getWriter();
if (facebookSignedRequest.getOauth_token() == null) {
	response.setContentType("text/html");
	writer.print("<script> top.location.href='"	+ FacebookAuthService.getAuthURL() + "'</script>");
	writer.close();
} else {
	request.setAttribute("accessToken",	facebookSignedRequest.getOauth_token());
	RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/YOUR_NEXT_PATH");
	requestDispatcher.forward(request, response);
}

So, if the Oauth token is not present in the signed request the servlet redirects user to get the app permission. Once user allows the app the servlet gets the Oauth token in the signed request and pass it to the next page of the app.

41 Responses to Facebook Canvas App Authentication – Java

  1. Bhagwat says:

    Hey,

    Thanks for the Great Stuff …………..

    The most important thing ….. it’s explained in simplest way ………..

  2. Toni Schiele says:

    Thank you so much for giving everyone an extremely spectacular opportunity to check tips from here. It really is so ideal and as well , full of a good time for me personally and my office mates to visit your site the equivalent of 3 times weekly to see the new items you have got. And indeed, I am just always astounded for the amazing guidelines you give. Selected 2 areas in this article are surely the most impressive I have had.

  3. Irena says:

    Hello Dyutiman,
    Thank you for the article, it helped me a lot!
    I am graduate student and need to develop Facebook application in order to collect some data for my research.
    I used your code but for some reason I have a problem at the first time user enters the application. This happens when the user do not have access_token, he receives it but not redirected to my page but to Facebook page with the access token written in the body of the page. I request for “read_stream,offline_access” permission, so next time the user enters he already has access token.
    My servlet code is:
    String signedRequest = (String) request.getParameter(“signed_request”);

    FacebookSignedRequest facebookSignedRequest = null;
    try
    {
    if ( signedRequest != null )
    {
    facebookSignedRequest =
    FacebookAuthService.getFacebookSignedRequest(signedRequest);
    }
    }
    catch (Exception e) {
    response.sendRedirect(“errorPages/noAccess.html”);
    e.printStackTrace();
    return;
    }

    PrintWriter out = response.getWriter();

    if ((facebookSignedRequest == null)
    || (facebookSignedRequest.getOauth_token() == null))
    //new user of the application
    {
    String authCode = request.getParameter(“code”);
    String error = request.getParameter(“error”);
    if (authCode != null) //the user allows extended permission, get accessToken
    {
    response.setContentType(“text/html”);
    System.err.println(“authCode =” +authCode);
    out.print(” top.location.href='” +
    FacebookAuthService.getAuthURL(authCode) +
    + “‘”);
    out.close();
    //response.sendRedirect(FacebookAuthService.getAuthURL(authCode));
    }
    else if (error != null) //the user does not allow extended permission
    {
    response.sendRedirect(“errorPages/noAccess.html”);
    }
    else //redirect the user to OAuth Dialog
    {
    response.sendRedirect(FacebookAuthService.getLoginRedirectURL());
    }
    return;
    }
    else //user already has accessToken
    {
    request.setAttribute(“accessToken”,
    facebookSignedRequest.getOauth_token());
    RequestDispatcher requestDispatcher =
    getServletContext().getRequestDispatcher(“/Main.jsp”);
    requestDispatcher.forward(request, response);
    }
    }

    • dyutiman says:

      Hi,

      I do not see much problem in your servlet code. What I will ask you to check closely is your canvas page url and authUrl. The problem seems to be with the redirect url. Read the Facebook manual again and again so you do not miss anything.

      • Irena says:

        Thank you for the fast reply.
        I checked it few time:
        My canvas url is the url to the servlet I wrote above (I will call it CANVAS_URL), it work when the user already have accessToken (enters the second time)
        The flow is :
        1) If the user does not have access_token in the signed_request parameter or the signed_request parameter is null – I redirect the user to oauth dialog:
        https://www.facebook.com/dialog/oauth?client_id=200234090057839&redirect_uri=CANVAS_URL&scope=read_stream,offline_access
        2) Then, Facebook should redirect to the same servlet CANVAS_URL, with “code” parameter (in case the user give the permission) or “error” parameter (if the user does not allow the permissions).
        3)In the servler I take the “code” parameter and redirect the request back to Facebook in order to get the accessToken for the user:
        https://graph.facebook.com/oauth/access_token?client_id=200234090057839&redirect_uri=CANVAS_URL&client_secret=XXX&code=
        4) Then, Facebook should redirect to the same servlet CANVAS_URL, with "access_token" parameter and I will forward it to my next jsp page as in case the user already had access_token in the signed_request! BUT, I it does not redirect it to me!! I just getting the access_token=XXX inside CANVAS page, as it's content.

        What I miss here? Why I do not get step 4?
        thanks,
        Irena

        • dyutiman says:

          Hi,

          As I told you to read Facebook manual closely. Take a look again @https://developers.facebook.com/docs/appsonfacebook/tutorial/#auth.

          You won’t get the “code” parameter. in step 2 you will get the “signed_request” parameter instead. You do not have to get the signed_request parameter explicitly.

          If in your app you need to to have the accessToken again, you need to read the output of the call in step 2.

          • Irena says:

            Hi again,
            I read the flow I described here (about 20 times) : https://developers.facebook.com/docs/authentication/
            “If the user presses Allow, your app is authorized. The OAuth Dialog will redirect (via HTTP 302) the user’s browser to the URL you passed in the redirect_uri parameter with an authorization code:
            http://YOUR_URL?code=A_CODE_GENERATED_BY_SERVER
            With this code in hand, you can proceed to the next step, app authentication, to gain the access token you need to make API calls.
            In order to authenticate your app, you must pass the authorization code and your app secret to the…”
            My question is: how I get the “authorization code”? Do I need it?
            I will try to change the code according to what you told..
            thanks a lot,
            Irena

            • dyutiman says:

              Yes, you are right. I agree Facebook documentation is little bit messy. But the article you are referring here is for Facebook Connect i believe. If you follow the link I mentioned, your problem will be resolved.

              • Irena says:

                I tried to follow the link you gave me but it did not work :(
                When I need extended permission like “read_stream,offline_access” I should get the “code” parameter and only then send it back to Facebook together with my app “secret”.
                When I used the flow described in https://developers.facebook.com/docs/appsonfacebook/tutorial/#auth
                I got infinite loop in the page, the URL was with “code” parameter.
                So I returned to the flow I described above (with the bug that first time the user enters he see the access_token=xxx page, instead of the application.)
                If you have time, maybe you can try to check your example for the scenario extended permissions are needed.

                thanks again,
                Irena

                • dyutiman says:

                  Hi Irena,

                  I do not really feel like changing my code, its working perfectly. I think you are making yourself confused here. So let me try again to explain the flow to you.

                  1. User logs in to Facebook and clicks a link to go to your app. You get the request to your entry servlet.
                  2. you now check if you get the “signed_request” through “request.getParameter”. If you get it then check if “oauth_token” is available in “signed_request”. If its there you are done.
                  3. if “signed_request” does not have “oauth_token” or if “signed_request” is not present you redirect the user to https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_CANVAS_PAGE&scope=read_stream,offline_access
                  4. now user will be asked to grant your app and if user allows your app Facebook will redirect the user to YOUR_CANVAS_PAGE which is your entry servlet. Now go to step 2 and you will get the “signed_request” containing the “oauth_token”.

                  This is my way of doing the thing. If you do not like it and want to stick to your flow then I think at your step 3 do not redirect the user to https://graph.facebook.com/oauth/access_token?client_id=200234090057839&redirect_uri=CANVAS_URL&client_secret=XXX&code=. Instead think the url as a location of a file and try to read the file from the url. The file contains the “access_token”. You parse the file, get the access_token and your are ready to go.

                  Let me know if I am able to make it clear to you here.

                  • Irena says:

                    Hello Dyutiman,
                    I prefer your flow because it is shorter, the problem is that I do not get the “signed_request” containing the “oauth_token” after user allow permission.
                    I just get the “code” parameter, so I will try the second option you suggested.
                    Do you know how I can send new request and wait for the response and not continue the servlet code?

                    thank you very much,
                    Irena

                    • dyutiman says:

                      I think I got your problem. Would you mind sharing the url you are passing as “redirect_uri” parameter please. if you do not want to disclose it in open forum you can mail me @ dyutiman.chaudhuri@gmail.com

                    • Chad says:

                      Did you resolve this? I kept having a similar problem — I’d get a request to my canvas URL (e.g. http://localhost/ not the canvas page https:/apps.facebook.com/myappid) with a “code” argument right after the user authorized the app. It was only at that point that the problem happened — if a user went directly to http://apps.facebook.com/myappid some time after authorizing, everything worked. (This is all when I used the canvas URL http://localhost/ as the &redirect_uri= argument.) I couldn’t find any way around that — the simpler flow you described didn’t ever happen.

                      So I hacked a solution, which is that if my server gets a request with the code parameter, it just redirects the user’s browser to http://apps.facebook.com/myappid using
                      writer.print(” top.location.href='” + appUri + “‘”);

                      Also, the example is a little confusing since you have two functions that never seem to be used:
                      getLoginRedirectURL()
                      getAuthURL(String authCode)

                      since only getAuthURL() — without the authCode argument — is used. Was that a relic of an older version?

                      Despite the confusion, this is the only thing that was able to get me going in terms of the mechanics of an app in java, so thank you!

                    • dyutiman says:

                      Yes, the issue was solved. You please re check your redirect_uri, it should be something like http://apps.facebook.com/YOUR_APP. After the permission page Facebook should redirect user to this page.

  4. RuiXian BAO says:

    Hello Dyutiman,

    I have another question in writing my application. I know that the last part of your code for checking is made for the doPost() method, but now I tried to apply it to the doFilter() method. I want to use a filter to do the checking. My problem is that even I can get the signedRequest, I still cannot get the accessToken via FacebookSignedRequest. It seems that there is an initialization problem with the FaceSignedRequest class. Is it at all possible to apply your code to a filter? The following is an excerpt of the log:

    Best

    – RuiXian

    org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “max” (Class com.accenture.facebook.domain.FacebookSignedRequest$FacebookSignedRequestUser$FacebookSignedRequestUserAge), not marked as ignorable
    at [Source: java.io.StringReader@36788aed; line: 1, column: 114] (through reference chain: com.accenture.facebook.domain.FacebookSignedRequest[“user”]->com.accenture.facebook.domain.FacebookSignedRequestUser[“age”]->com.accenture.facebook.domain.FacebookSignedRequestUserAge[“max”])
    at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
    at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:649)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:635)
    at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1355)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:717)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
    at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
    at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
    at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
    at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:414)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
    at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2723)
    at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1854)
    at com.accenture.facebook.service.FacebookAuthService.getFacebookSignedRequest(FacebookAuthService.java:49)
    at com.accenture.facebook.service.FacebookUserFilter.doFilter(FacebookUserFilter.java:128)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:864)
    at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:579)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1665)
    at java.lang.Thread.run(Thread.java:636)

    • dyutiman says:

      i’ve updated the FacebookSignedRequest class. take it and let me know if its working.

      • RuiXian BAO says:

        Hello Dyutiman,

        Great news. It works like a magic now using the updated version. Thank you again! Not sure why doFilter() needs this max property while doPost() does not though:)

        Best

        – RuiXian

  5. RuiXian BAO says:

    Hello Dyutiman,

    I am using your code to write a java application. But I have a problem. After dispatching to a jsp page from my Canvas entry servlet, everything is okay. In my jsp page, I have the form to submit, and the target is the same entry servlet. But then I get a java null point error after submitting. This is caused by failing to get facebookSignedRequest.getOauth_token(). The first time succeeded, but not the the second time with submitting. Any idea? The following is the excerpt of the log file. Thanks again for your good work.

    Caused by: org.json.JSONException: JSONObject[“session_key”] not found.
    at org.json.JSONObject.get(JSONObject.java:285)
    at org.json.JSONObject.getString(JSONObject.java:411)
    at com.google.code.facebookapi.ExtensibleClient.auth_getSession(ExtensibleClient.java:331)
    … 17 more
    java.lang.NullPointerException
    at com.accenture.facebook.service.FacebookAuthService.getFacebookSignedRequest(FacebookAuthService.java:45)
    at com.accenture.facebook.MainPageServlet.doGet(MainPageServlet.java:74)
    at com.accenture.facebook.MainPageServlet.doPost(MainPageServlet.java:151)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    • dyutiman says:

      Hi,

      you will get the access token when Facebook sends a redirect to your entry servlet and then u get the access token in your post method handler. If you submit the page to entry servlet by urself u r not gonna get that access token.

      What I will suggest here is once you get the access token do not ask for it again until the time expires.

      • RuiXian BAO says:

        Hello Dyutiman,

        Thank you for your prompt reply.

        The problem is not only that I cannot get the access token, but also the signedRequest is not available anymore when submitting to the same entry servlet from the jsp page. The number 45 line in FacebookAuthService.java is as follows:

        String payLoad = signedRequest.split(“[.]”, 2)[1];

        Is this also the supposed behavior?

        Best

        – RuiXian

        • dyutiman says:

          Yes, you wont get the signedRequest itself. If you want to have that you have to make a redirect to the auth url and from there Facebook will redirect to your canvas page and then u can have the signed request.

          let me know if i am making any sense here….

          • RuiXian BAO says:

            Yes, very much. So far I don’t have such a need yet, if I need later, I will try this approach. Now I have only managed to let the entry servlet not try to get signedRequest on the subsequent calling. So far so good, the error has gone. Thanks for the professional help. Perhaps later on will ask you more questions:)

            Best

            RuiXian

  6. Rachel H says:

    A few weeks ago i was attempting to make a canvas that used a website which had Java script running on it. One of the job agencies I was advertising for stressed how badly the wanted a page with this on it. There was no error for the Java , however, since they now require that you provide a site with SSL and HTTPS I was not allowed to add it to my page. Has Java support been removed? I feel as though it still supports it.

    • dyutiman says:

      Hi Rachel,
      Frankly speaking, its not very clear to me what exactly you are looking for. But from what I understood, there’s nothing depending on Java or any particular language. Please elaborate your point.

  7. MRT says:

    I got the following Error:
    java.lang.NoSuchMethodError: org.codehaus.jackson.map.BaseMapper.(Lorg/codehaus/jackson/JsonFactory;)

    by this statement:
    new ObjectMapper().readValue(jsonString, FacebookSignedRequest.class);

    how to resolved it

  8. Comfortably, the post is during truthfulness a hottest on this subject well known subject matter. I agree with ones conclusions and often will desperately look ahead to your . Saying thanks a lot will not just be sufficient, for ones wonderful ability in your producing. I will immediately grab ones own feed to stay knowledgeable from any sort of update versions. get the Work done and much success with yourbusiness results!

  9. Raju Challagundla says:

    hi, this blog is really helpful to me but didn’t get the code of Base64Decoder . and ObjectMapper, so can you please share the code with us?

    • dyutiman says:

      I’ve changed the base64 decoder code as it was using Oracles internal jars. Now I am using apache’s common codec to encode the string.
      The packages you are looking for will be

      org.apache.commons.codec.binary.Base64;
      org.codehaus.jackson.map.ObjectMapper;

      • Raju Challagundla says:

        Hi dyutiman,
        Thank you for your quick reply..have one more doubt too..when I’m clicking Allow/ Don’t allow, it is calling the servlet’s ‘doGet’ method in local and my redirect uri is “http://localhost:8080/FaceBook/OAuthHandler”.I’m getting the output as local page not the facebook application page..how to redirect it to the facebook application page?…where as in normal case, it is working properly..any help is appreciable.

        • dyutiman says:

          I am not sure if I got the point you want to make here. But your redirect_uri should be your Canvas Page which you have set in your application settings in Facebook. So once user presses the allow button, the app will redirect the user to your Canvas Page and you will get the signed_request in your doPost method.
          In other case if you want to provide me all the details and don’t want to disclose your code or logic here, you can mail me @ dyutiman.chaudhuri@gmail.com

          • Raju Challagundla says:

            once pressing the allow button it is not calling the canvas page but not going to the doPost Method instead it is going through the doGet but if it is already allowed then it is calling doPost method…hope it is clear now

            • Raju Challagundla says:

              ignore earlier…

              once pressing the allow button it is not calling the canvas page but not going to the doPost Method instead it is going through the doGet but if it is already allowed then it is calling doPost method…hope it is clear now

              • Raju Challagundla says:

                once pressing the allow button it is calling the canvas page but not going to the doPost Method instead it is going through the doGet but if it is already allowed then it is calling doPost method…hope it is clear now

  10. Camille says:

    totally agree with you.

    My blog:
    rachat credit lyon et taux de Rachat De Credit

  11. Hello there, just became alert to your blog through Google, and found that it is truly informative. I am going to watch out for brussels. I’ll appreciate if you continue this in future. A lot of people will be benefited from your writing. Cheers!

Leave a reply to dyutiman Cancel reply