Sunday, 31 March 2013

[How To] - Implement Facebook login in JSF using SocialAuth

As a matter of fact, Facebook does not provide an official library for Java developer. To make the matter worse, the provided official Javascript SDK is not very easy to use (at least for me) or to integrate with a JSF application. Luckily, there are several unofficial projects going on at the moment and in my opinon, the best of all should be either RestFB or SocialAuth. Between these two alternatives, I prefer SocialAuth because of 2 reasons. First of all, RestFB doesn't support authentication. In other words, using RestFB, we have to dive into the JavaScript SDK mess to fetch the accessToken from Facebook on our own. Another thing is that SocialAuth supports not only Facebook but also a wide range of other providers such as Google, LinkedIn, etc. So, today, I will use SocialAuth to show you how to implement Facebook login in JSF.

One thing to note is that on SocialAuth's wiki page, there is actually a step-by-step tutorial on connecting to Facebook in a JSF CDI application. I've tried the provided solution but it didn't work out for me. Somehow, I ran into the Unsatisfied dependencies for type [SocialAuth] with qualifiers [@Named] at injection point [[field] error and no one has yet to provide an answer for my question on StackOverflow. So in this post, I will show you another way that I have figured out to connect to Facebook.

In brief, there are 4 steps that you need to follow:
  1. Create an application on Facebook.
  2. Download the latest SocialAuth SDK. In this tutorial, I am using v4.0.
  3. Copy, paste and edit!
  4. Enjoy your achievement :).
Firstly, you need to create an application on Facebook.


From the above page, you need to write down your App ID and App Secret, which would be used to connect to Facebook later. Another thing to note is that the Site URL property needs to be the same as the domain where you deploy your application. So, if you want to test your application locally, simply enter http://localhost:8080/.

After downloading the latest SDK from SocialAuth, which is v4.0 at the moment of this writing, you can proceed to the most interesting part which is coding!

In order to redirect users back to where they have been before logging in, copy and paste this code inside your <f:view>:


After that, copy and paste the following snippet where you want to put your Social login buttons:


One important thing to note is that you should put the login buttons on the top most of your page where no other components can be loaded before these buttons. Otherwise, you may run into java.lang.IllegalStateException when our socialConnect() function attempts to redirect the user if part of the response has been committed. You can also edit the above snippet to add in more buttons for other providers if needed. This is the list of IDs of all providers:


The following is the socialLoginSuccess.xhtml page where users would be redirected to on successful login:


One very important thing to note is that in the above success page, I didn't have the <f:event> component. If your success page uses the same template, which contains the <f:event>, as other pages, you will run into the following exception:


The above exception occurs because the originalURL was wrongly updated to be the authenticationURL. In other words, you will be authenticated one more time.

Finally, this is our @SessionScoped managed bean:


Based on this sample for the oauth_consumer.properties file, you should be able to figure out the properties to put in the props easily. In fact, instead of using Java's Properties, there are also several other ways to load the configuration. The complete list includes:


Enjoy your work! :)

54 comments:

  1. Very good work.Do you have any Demo war file for above code?

    ReplyDelete
    Replies
    1. Sorry, I haven't created a Demo war project yet. The above example code was written based on the current code in 1 of my projects :).

      Delete
  2. Hi james , one question here in your jsf page you added gmail as well so same method socialConnect will work for , gmail, yahoo as well as I saw you added Facebook authentication inside that method if I am wrong please tell me Thanks

    ReplyDelete
    Replies
    1. Yes, the `socialConnect` method will work for all providers. You just need to load all the required `consumer_key` and `consumer_secret` for any providers you want to use :)

      Delete
    2. Thanks one more thing i have to ask let us suppose if someone authenticated how can we tell our tomcat server the person is authenticated no need to to show the login page again i am using security constraint feature of web.xml file

      Delete
    3. I have never used container-based security before. However, I remember there is a function called `HttpServletRequest#login()`, which can be used to do a programmatic login. I think the best place to called that function is inside the `pullUserInfo()` method :).

      Delete
  3. Hi James, good work, thank you for this post.
    Can you upload project example , simple one. It would help me a lot.

    ReplyDelete
    Replies
    1. :) Sorry, I created this tutorial when I was doing my school project. I didn't create any testing project to upload. Everything should work fine if you follow all the steps in my tutorial :D.

      Delete
  4. I'm new in this technology
    where do you store FACEBOOK_APP_ID constant,
    "FacesContext.getCurrentInstance().getExternalContext().redirect(url);"
    url - connot resolve this var.
    Thank you.

    ReplyDelete
    Replies
    1. :) For testing purpose, you can simply add a line to declare a new variable like this:

      String FACEBOOK_APP_ID = "YOUR_FB_ID";

      In real project, it should be stored in a .properties file.

      Delete
  5. I am using the same example of using socialauth for facebook login for jsf web application. However i am not able to execute the application successfully because of the below issue.
    {
    "error": {
    "message": "redirect_uri isn't an absolute URI. Check RFC 3986.",
    "type": "OAuthException",
    "code": 191
    }
    }

    Please help me in resolving this issue. Provide me your mail id to send the complete WAR file for you to look into. I need to resolve this issue as early as possible. My environment is JDK6+ Tomcat 7

    ReplyDelete
    Replies
    1. wich URL are you sending?
      This error occured with me when I didn't put http:// on begining of your URL

      Delete
  6. Hello, Thank You !!
    This works, but

    Please help me with below mentioned scenario :

    1. Login to "MyApp" using socialAuth. ( exactly explained above).
    2. Open facebook in a new tab, and logout.
    3. Come back and refresh the "MyApp" login Page.
    4. Still shows the session attributes.

    Please suggest.

    ReplyDelete
    Replies
    1. The Facebook session in your own application and the Facebook session itself are not the same session. When you log out of your Facebook account, Facebook doesn't send any notification to your own application. Hence, you're still considered as "logged in" in your own application.

      At this point, I'm not so sure if logging out from Facebook will effectively log you out from the Facebook session in your own application as well. You need to do some experiment to test this assumption :). If it's confirmed, you can try adding a "preRenderView" event on every page to test if the Facebook session is still alive. If "not", redirect back to the "Login" page :).

      Delete
  7. Hi James, thanks for the tutorial.
    My problem is that after redirecting to socialLoginSuccess.xhtml manager in pullUserInfo() method equals null and i don't know why.
    Can you suggest something?
    (I don't know if it does matter, but I'm using form-based authentication on my glassfish server)

    Thanks in advance

    ReplyDelete
    Replies
    1. Hmmm... I think you should add the following line:



      on your pages before and after logging in to test if you're accessing the same bean. I guess there's something wrong and a new "userSessionBean" was created.

      Delete
    2. Can you paste this line again? I don't see it.

      Delete
    3. You were right. I've debug my project and like you said, the userSessionBean was recreated every time. Don't know what was wrong but I've just simply removed it and created once again and now it works just fine.
      Thanks for the clue :)

      Delete
    4. Hi James, thanks for the great post when there is NO help available to get it working end to end. I appreciate your efforts.
      I am also having the same issues when in which manager becomes null after redirection. I just read the answer by "Anonymous" above but not able to understand what he means by removing userSessionBean and recreating. Can you help please?

      Delete
    5. @Abhishek: You should try to do a 'System.out.printnl(this)' inside a '@PostConstruct' method of the 'UserSessionBean'. There may be something wrong with your code and the session bean is re-created every request. In that case, the above line will print out different values for each request.

      Delete
    6. Thanks for the response James. Ya thats what is happening is for sure but it happens with chrome and firefox only it works perfectly ok with IE. I think a lot of people has faced this issue and someone need to fix, document and publish this online so that people dont waste a lot of time. Can you share me your code vi email James at abhishekdhote@gmail.com?

      Delete
    7. There is something which I am doing differently is I am using '@ManagedBean(name="userSession")' instead of '//@Named(value = "userSession")' does that have impact on the issues.

      Delete
    8. Also can you confirm the dependent libraries? The libraries I am using are socialauth-4.3.jar, commons-logging-1.1.jar, json-20090211.jar, openid4java-consumer-0.9.5.jar

      Do we need openid4java.jar as well apart from openid4java-consumer-0.9.5.jar?
      Mine is a JSF2 + Primefaces 3 application.

      Delete
    9. @Abhishek: I'm sorry but I don't have the code anymore :). It was a one-time for-fun project but still, I've included all the necessary code in my post. I didn't have any problems with Firefox or Chrome.

      There should be something really wrong that caused a @SessionScoped bean to be re-created every request. I think you should ask your question on Stackoverflow for a more detailed answer :)

      Delete
    10. Thanks James really appreciate you response.

      Delete
  8. Hello, thanks for the great tutoril i now connect to facebook but cannot pull user info. First of all the line this.profile = provider.getUserProfile(); gives an error because it cannot find any function called getUserProfile(): Secondly, when it comes to the line AuthProvider provider = (AuthProvider) manager.connect(map); in the pullUserInfo() function, it throws an exception saying that "SocialAuthException: Verification code is null". What can i do about them can you help? Thanks

    ReplyDelete
    Replies
    1. It seems like your library or your programming environment is corrupted or you may have downloaded the wrong version. I'm pretty sure "getUserProfile()" is a valid method of the "AuthProvider" class. You can view the class file here: http://tinyurl.com/AuthProvider-java

      Your 2nd exception might happen because of the same reason too. Try to fix the 1st problem and let me know later if you still have the 2nd problem :).

      Delete
    2. thanks for reply, i had downloaded socialauth-java-sdk-4.2 since it is the latest version i think you use version 4.0. In the downloads page it says 4.0. is deprecated would it be problem i download and use version 4.0?

      Delete
    3. well, i tried version 4.0 but still cannot find getUserProfile. I may be adding the library in a wrong way, here is what i do. I go to libraries folder of my project, right click and say add library. Then i say create new library and give it some random name like "social". Then here i have this window: http://imgur.com/UXwiJsZ and say add jar/folder while i am at ClassPath tab and add the whole folder. I do not add anything to the Sources and Javadoc tabs. Is that the way we include the library in the project? or am i doing something wrong? Thank you

      Delete
    4. and lastly (sorry for too many posts) you have a line in your code like this: FacesContext.getCurrentInstance().getExternalContext().redirect(url); but there is no url defined in the code, and so i had changed url variable to authenticationURL. May that be the problem?

      Delete
    5. You're right that "url" should be the "authenticationURL". It's a typo on my side :). I was too busy to answer your previous questions. I hope you finally got it right. If you need to use v4.2, please check the API to make sure the "getUserProfile()" method has not been removed. If it's not, there must be something wrong in your class path.

      Delete
  9. Hello..
    FacesContext.getCurrentInstance().getExternalContext().redirect(url);
    what is url??
    plz help me

    ReplyDelete
    Replies
    1. :) It's the "authenticationURL". I have updated the code.

      Delete
  10. Thanks for this article, I'm using facebook4j API + jsf + hibernate. This example helped with this java.lang.IllegalStateException.

    ReplyDelete
  11. I just follow what you say and my school project is running very well, but can SocialAuth creates a facebook like or share button?

    ReplyDelete
    Replies
    1. ;) I knew you'd get it working sooner or later. Unfortunately, for "Like" and "Share" buttons, you need to use Facebook's API. Take a look at the documentation here: https://developers.facebook.com/docs/plugins/

      Delete
  12. Hello James.

    Your blog is full of interesting stuff. This article in particular has helped me quite a lot.

    Do you have a solution for using the same page (e.g. index.xhtml) for login AND callback from Facebook?

    I've been stuck on this for quite a while. My question is also posted on StackOverflow.
    http://stackoverflow.com/questions/23747930/jsf-login-with-facebook-socialauth-and-keeping-session-alive

    When you have the time, please take a moment to review it.

    Thank you.

    ReplyDelete
  13. Hello James.
    Thanks for the instructions. I followed it and it worked perfect for facebook. however accessing google is giving a 400 error OpenID auth request contains an unregistered domain:
    I googled the problem and it looks the cause of it is due to the use of old openID instead of OAuth 2. this is confusing because SocialAuth should be using OAuth 2. I guess the code should be changed a little to direct socialAuth to use OAuth 2 for Google authenticating but I couldnt figure out a solution. Any input?
    Thanks in advanced

    ReplyDelete
    Replies
    1. Hi, I tried to google around as well as check the release notes of SocialAuth but I found nothing regarding your problem :S. When I used the above code for my school project, it worked fine for Google Authentication.

      Let me know if you found a solution later :)

      Delete
    2. Thanks a lot. I did find a solution.
      Basically after digging and digging I found out the issue is caused by the authentication url which for some reason is not directed to a Oath2 endpoint.
      And from the code we can see that the authentication url is determined by the providerID. so I guessed the solution must be in changing the provider ID and I was correct after more googling I found out that the providerID should be googleplus not google and the properties should be
      props.put("googleapis.com.consumer_key", GOOGLE_APP_ID);
      props.put("googleapis.com.consumer_secret", GOOGLE_APP_SECRET);
      props.put("googleapis.com.custom_permissions", "https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email");
      I think SocialAuth forgot to update their code when the providerID is google

      Delete
  14. This comment has been removed by a blog administrator.

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

    ReplyDelete
  16. Thank you for this great work.It is working for facebook. If we want to use for twitter facebook and google ,can you say me how to do that? If I use this structure, for example I do login with facebook adn logout firstly.Secondly I click the twitter login command link, It says "twitter is not a provider or valid OpenId URL". If I use twitter fistly, Same thinng come out for facebook. What is way of doing this?

    ReplyDelete
    Replies
    1. Do you resolved it? My problem is "...linkedin is not a provider or valid OpenId URL"

      Delete
    2. Do you resolved it? My problem is "...linkedin is not a provider or valid OpenId URL"

      Delete
  17. When trying example on SocialAuth's page, there is a notice from google:

    "Important notice: OpenID2 for Google accounts is going away on April 20, 2015. Learn more."

    Do you know if SocialAuth will support new "Google+ Sign-in" api?

    ReplyDelete
  18. i'm getting this error any help??
    {
    "error": {
    "message": "redirect_uri isn't an absolute URI. Check RFC 3986.",
    "type": "OAuthException",
    "code": 191
    }
    }

    ReplyDelete
  19. hello, i'm trying to call public void pullUserInfo() on the succes page after a succes redirect but it throws this:

    UserSession - Exception: org.brickred.socialauth.exception.SocialAuthException: javax.net.ssl.SSLHandshakeException: General SSLEngine problem

    i've checked developer.facebook.com and my app-s url is the same that is in my code..

    i'm trying with local weblogic12c on ssl with my own cert that i created with keytool..

    does anybody has any idea to solve this ?

    Thx!

    ReplyDelete
  20. Hi. Thanks for your help. I'm already almost make it work the authentication in my app. However, now I'm getting the following error:

    UserSession - Exception: org.brickred.socialauth.exception.ServerDataException: Failed to parse the user profile json : {"name":"Jluis Moran","id":"866697406790434"}

    ReplyDelete