Pages

Saturday, June 16, 2012

Java-Spring Security Authenticate through Facebook- OAuth 2



Recently I had a requirement of adding "Login with facebook" functionality to my existing spring-java web application. As I was not familiar with this before, tried to find some online examples or tutorials to get some help. But I couldn't find any good tutorial on this and finally had to go through the facebook api and restfb  by myself to find a solution. Since the solution I found is working fine so far, thought to share it hoping it might be useful to someone else too.

My web application was a spring mvc (spring 3) configured and also spring security-3. I had a normal login form to the web app through spring security (/j_spring_security_check). All I wanted was to have a "Login/Connect with Facebook button". My requirement was to authenticate the user in my spring security context after he has been authenticated through Facebook. For this purpose I used a workaround since I couldn't find any other proper solution-even I tried various methods, even Spring social.  In spring social they have specified a very convenient way to interact with Facebook's Graph API once we get the access token from facebook and the access token can be received from Facebook after OAuth authorization. This is exactly where I got stuck. So below is the way I got through it.

I'm going to use server-side authentication with Facebook in this example. 

As the first step, we will have to create an application with Facebook. This application will represent our real application in Facebook, and will give the "App ID/API Key"  and "App Secret" which will be required to connect our application with Facebook.

For this, go to "https://developers.facebook.com/apps" and create a new application by clicking on "Create New App". After entering the required details, you will be given the App Id and App Secret.

Next, according to the Facebook API we have to redirect user to the URL-
"https://www.facebook.com/dialog/oauth?
    client_id=YOUR_APP_ID
   &redirect_uri=YOUR_REDIRECT_URI
   &scope=COMMA_SEPARATED_LIST_OF_PERMISSION_NAMES
   &state=SOME_ARBITRARY_BUT_UNIQUE_STRING"

in order to get the OAuth Dialog from facebook-(the user will enter the Facebook user name and the password here)

What I did was, first redirecting the user to my own controller and redirecting to the above url from my controller-In this way I thought we can get more control over constructing the above url.

So, as the first step, add the something like below in your JSP where the login with Facebook button is required,

<a href="facebookLogin.htm">
<img src="images/fbconnect.png" alt="Connect with Facebook" />
</a>

will appear like :- 
Now the code inside the controller will be like below;

@RequestMapping(value = "/facebookLogin")
public void getFacebookLogin(HttpServletRequest request,HttpServletResponse response) {
String url="https://www.facebook.com/dialog/oauth/?"
 + "client_id=" + ApplicationConstants.FACEBOOK_APP_ID
 + "&redirect_uri=" + ApplicationConstants.FACEBOOK_REDIRECT_URL
 + "&scope=email,publish_stream,user_about_me,friends_about_me"
 + "&state=" + ApplicationConstants.FACEBOOK_EXCHANGE_KEY
 + "&display=page"
 + "&response_type=code";
try {
response.sendRedirect(url);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Lets have a look at the variables appended to the url,

client_id-this is the application id ("App ID/API Key") mentioned above, since this will not change throughout the application, i have put it as a constant in my ApplicationConstants.java interface.
redirect_uri- this will be the url, to which the user will be redirected by Facebook after login/cancellation from the OAuth Dialog. Our next set of code needs to be put here, how to handle the post authentication process. I used like "http://localhost:8080/your_app_name/facebookAuthentication.htm"
scope-The scope parameter allows us to specify a comma separated list of additional permissions which we need the user to grant our application, the complete list can be found here. Add these according to your requirements.
state-this is like a key we used to keep the conversation between our application and Facebook safe, "to ensure the security of the response when the user returns to your app after the authentication step". This can be any string eg:- "justtotestfbresponseafterlogin"-this too is a constant
display- The display mode with which to render the Dialog
response_type-The requested response type, one of code or token, I tried to get the token staright, but I failed, so I'm getting the "code" and using the code we receive, we can get the access token.

Here can be found a complete list of parameters with details to the above URL.

Now its time to see howto handle the response we receive from Facebook when the user redirect to our application from the above URL. this is my code in the controller,

@RequestMapping(value = "/facebookAuthentication", method=RequestMethod.GET)
public String facebookAuthentication(HttpServletRequest request,HttpServletResponse response) {
//Get the parameter "code" from the request
String code=request.getParameter("code");
        //Check if its null or blank or empty
if(StringUtils.isNotEmpty(code)){
 //If we received a valid code, we can continue to the next step
               //Next we want to get the access_token from Facebook using the code we got, 
//use the following url for that, in this url,
//client_id-our app id(same as above), redirect_uri-same as above, client_secret-same as //above, code-the code we just got
String url="https://graph.facebook.com/oauth/access_token?"
                 + "client_id=" + ApplicationConstants.FACEBOOK_APP_ID
                 + "&redirect_uri=" + ApplicationConstants.FACEBOOK_REDIRECT_URL
                 + "&client_secret=" + ApplicationConstants.FACEBOOK_SECRET_KEY
                 + "&code=" + code;
// Create an instance of HttpClient.
        HttpClient client = new HttpClient();
       // Create a method instance.
        GetMethod method = new GetMethod(url);   
       // Provide custom retry handler is necessary
       method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
    new DefaultHttpMethodRetryHandler(3, false));   
       try {
     // Execute the method.
     int statusCode = client.executeMethod(method);
     if (statusCode != HttpStatus.SC_OK) {
       System.err.println("Method failed: " + method.getStatusLine());
     }
     // Read the response body.
     byte[] responseBody = method.getResponseBody();
     // Deal with the response.Use caution: ensure correct character encoding and is
              // not binary data
     String responseBodyString=new String(responseBody);
     //will be like below,                                 //access_token=AAADD1QFhDlwBADrKkn87ZABAz6ZCBQZ//DZD&expires=5178320
    //now get the access_token from the response
     if(responseBodyString.contains("access_token")){
     //success
     String[] mainResponseArray=responseBodyString.split("&");
     //like //{"access_token= AAADD1QFhDlwBADrKkn87ZABAz6ZCBQZ//DZD ","expires=5178320"}
     String accesstoken="";
     for (String string : mainResponseArray) {
if(string.contains("access_token")){
accesstoken=string.replace("access_token=", "").trim();
}
  }
   
     //now we have the access token :)
                 //Great. Now we have the access token, I have used restfb to get the user details here
     FacebookClient facebookClient = new DefaultFacebookClient(accesstoken);
     User user = facebookClient.fetchObject("me", User.class);
                   //In this user object, you will have the details you want from Facebook,  Since we have    the  access token with us, can play around and see what more can be done
//CAME UP TO HERE AND WE KNOW THE USER HAS BEEN AUTHENTICATED BY FACEBOOK, LETS AUTHENTICATE HIM IN OUR APPLICATION
    //NOW I WILL CALL MY doAutoLogin METHOD TO AUTHENTICATE THE USER IN MY SPRING SECURITY CONTEXT
     }else{
     //failed
                   return "redirect:loginPage.htm";
    }     

   } catch (HttpException e) {
     System.err.println("Fatal protocol violation: " + e.getMessage());
     e.printStackTrace();
   } catch (IOException e) {
     System.err.println("Fatal transport error: " + e.getMessage());
     e.printStackTrace();
   } finally {
     // Release the connection.
     method.releaseConnection();
    }
}else{
 //failed
return "redirect:loginPage.htm";
}
}

Now I will show the code i used to authenticate the user in my spring security context,

public String doAutoLogin(String username, String password, HttpServletRequest request)throws Exception {
try {
// Must be called from request filtered by Spring Security,
// otherwise SecurityContextHolder is not updated
  UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(new WebAuthenticationDetails(request));
Authentication authentication = this.authenticationProvider.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
return ApplicationConstants.SUCCESS;
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
System.out.println("Failure in autoLogin" +  e);
}
return null;
}


Hope this tutorial helps, any feedback is highly appriciated.

In the next tutorial, lets see how to access various information from Facebook and how to publish to Facebook using restfb api and hopefully Spring social too. 

6 comments:

  1. Very good post, very enlightning.
    That's exactly what I was looking for (since I have the very same request from my application).

    Regards,
    DiegoPig

    ReplyDelete
  2. Getting Unsupported media Type Error

    ReplyDelete
  3. It's a good example of facebook oauth, but you can find spring-security-oauth2 which implements what you did. More configure and less coding.

    ReplyDelete
    Replies
    1. Hi Karl Li,

      Sorry if I did not understand you but I made a clone of spring-security-oauth2 project (git clone git://github.com/SpringSource/spring-security-oauth.git). The applications oauth1 and oauth2 use static users "marissa" and "sam" to authenticate. Do you know a tutorial to change the auth mechanism to facebook instead these static users?

      Regards,
      Cassio

      Delete
  4. Thank you for this tutorial. However, I have some doubts. I am totally new to Spring Security and I would like to authenticate my users with both FB and Spring Security.

    In your example you have the comment //NOW I WILL CALL MY doAutoLogin METHOD TO AUTHENTICATE THE USER IN MY SPRING SECURITY CONTEXT

    How to call that method from there. From where do we get the username, password and the the HttpServletRequest objects?

    I am stuck with the FB integration and I cant find any useful posts. Almost all of them show a different approach. Any help is appreciated. Thank you.

    ReplyDelete
  5. From where do you get the Password to pass to Sprinv MVC Controller method /doAutoLogin ?

    ReplyDelete