Facebook Login, My Old Nemesis

An Alternative Login with Facebook Implementation

So every app of sufficient complexity has one feature that always seems to break in odd ways. You tweak it, duct tape it, adjust it, but it just breaks, breaks, breaks. When this happens enough, you tear it down and rebuild the silly thing from scratch. This is what happened with my Facebook login implementation for babynamester.com

Facebook-My-Nemesis

I used to have an implementation based on this: https://developers.facebook.com/docs/facebook-login/getting-started-web/

This default implementation runs on event listeners. For whatever reason, this version of Facebook login proved to be really fragile for me. After many hours trying to shore it up, I spent some quality time with the Facebook API documentation and built the version below instead.

My version works by passing callbacks to the FB.login function to perform an app login on success.

1. Create a Facebook app

Just follow the instructions here under section 1 of this page: https://developers.facebook.com/docs/facebook-login/getting-started-web/

2. Initialize the Facebook SDK on your page

Next, make sure your page initializes with Facebook’s API correctly. There’s no need to nest any of the javascript in a document ready function.

<div id="fb-root"></div>

<script type="text/javascript">
(function(d){
   var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
   if (d.getElementById(id)) {return;}
   js = d.createElement('script'); js.id = id; js.async = true;
   js.src = "//connect.facebook.net/en_US/all.js";
   ref.parentNode.insertBefore(js, ref);
}(document));
  
window.fbAsyncInit = function() {
   FB.init({
      appId      : '123456789123456789', // replace this with your appId
      channelUrl : '//'+window.location.hostname+'/channel.php', // Path to your Channel File
      status     : true, // check login status
      cookie     : true, // enable cookies to allow the server to access the session
      xfbml      : true  // parse XFBML
   });
}
</script>

3. Write a function to handle success

Let’s skip to the end before circling back. After we have a successful Facebook login, we’ll need to tell our app that we’ve validated a Facebook login for a specific user and create the appropriate session. At this stage we’ll have a Facebook user object to work with, ‘me’. We’ll see how we get that in step 4 below – just go with it for now.

Let’s make sure this object is valid (i.e. has an id). If it is, I hit an endpoint in the app to get a signature for that id. This is to prevent people from spoofing my app’s facebook login form and creating bogus accounts and sessions in the app. Once we get the signature (‘key’) back from the app’s endpoint, we enter the id, the signature, and some basic user information (found on the user object) into the login with facebook form and submit it. The form submits to an action that will validate the id against the signature. If it checks out, it will see if a user exists in the app. If not, we create one. And then either way we create a session.

The code I’m showing you is everything in the view layer. I leave the signature endpoint and login endpoint to your imagination. Those will be specific to your app anyway. This code also does not need to be in a wait for document ready.

<script type="text/javascript">

// this is the function to login to our app once the Facebook login
// is successful and we have a Facebook user object
function login_with_facebook(me) {
   // this is the endpoint in my app that I get a validation signature from
   url = '/facebook/jskey';

   if ('id' in me && me.id) {
      $.post(url, { 
         'id': me.id 
      }, function(response) {
         // use the user object data and jskey signature to post the #fb_form form
         if (me.name) {
            $('#fb_form input.fb_name').val(me.name);
         }
         if (me.id) {
            $('#fb_form input.fb_id').val(me.id);
         }
         if (me.gender) {
            $('fb_form input.fb_gender').val(me.gender);
         }

         var data = jQuery.parseJSON(response);
         $('#fb_form input.fb_key').val(data['key']);
         $('#fb_form').submit();
      });
   }
}
</script>

4. Hook up a login button or link and a logout link

Finally, we create a button or a link with the class ‘login_with_facebook’ somewhere on our page and another link with the ‘fb_logout’ class. To make it functional, we use this code.

<script type="text/javascript">
$(document).ready(function() {
   $('.login_with_facebook').click(function(e){
      e.preventDefault();

      // Get the login status, perform actions accordingly
      FB.getLoginStatus(function(response) {
         if (response.status === 'connected') {
            // user is logged into Facebook and has already authorized your
            // app. request a user object and call our app login function
            // with it
            FB.api('/me', function(me) {
               if ('id' in me) {
                  login_with_facebook(me);
               }
            });
         }
         else if (response.status === 'not_authorized') {
            // user is logged into Facebook, but has not authorized your app.
            // Facebook login, passing in a callback to fetch the user object
            // and perform an app login on success
            FB.login(function(response) {
               if (response.authResponse) {
                  FB.api('/me', function(me){
                     login_with_facebook(me);
                  });
               }
            });
         } else {
            // not logged in to Facebook. We do a Facebook login, passing in
            // a callback that fetches the user object and fires an app login
            // on success. The same actions as the previous case, but I'm
            // keeping them separate to more clearly show the three possible 
            // states you can be in relation to Facebook. You also may want
            // to handle these cases differently.
            FB.login(function(response) {
               if (response.authResponse) {
                  FB.api('/me', function(me){
                     login_with_facebook(me);
                  });
               }
            });
         }
      });
   });

   // who said breaking up is hard to do?
   $('a.fb_logout').click(function(e){
      // preventing default to make sure our logout request fires and
      // then redirects to where the link points
      e.preventDefault();
      FB.logout();
      window.location = $(this).attr('href');
   });
});

</script>

5. Knit it into your web app
The parts that I haven’t shown you are on the backend of my app. You’ll need to figure out what makes sense for your app. Specifically, the parts of my app I’m not showing you are:
1. the /facebook/jskey endpoint that creates a signature for an id. For best security, the signature for a given id should change over time.
2. the endpoint that the form submits to. This endpoint validates the id against the signature. If valid, it checks to see if the user exists yet for that Facebook id in our app. If so, it just creates a session and redirects back to the page. If not, it will also create a new user.

Also, I leave my #fb_form form, login button, and logout link html to your imagination.

We’re not handling failure with much sophistication, just ignoring it. In the places where failure is detected, you could easily add some code to flash the user a helpful message if you desire.

That’s it, I hope you find this useful.

Comments

comments

Powered by Facebook Comments