« Back to home

Facebook authentication using machinepacks in Sails.JS

When I got first started with Node.JS about two years ago, I came across Sails.JS and instantly fell in love with it. Two years later, it is still my "go-to" web framework when building a Node.JS application. If you're not familiar with Sails.JS, it provides a real-time MVC framework on top of Node.JS. It's basically the ASP.NET MVC for .NET or Rails for Ruby. While it has some pretty nice features out of the box (ie real-time capabilities using websockets, asset pipeline, CLI tools, etc.), the main selling points for me were the built-in conventions and solid, well-thought architecture. For example, /api/Controllers/UserController.js corresponds to the end-point of /api/user that has views of /views/User/index.ejs and so forth. I'll be discussing some of these nifty features in future posts.

Authorization options in Node.JS

Sails.JS also comes with built-in policies for controlling API permissions which is the authorization piece that goes hand-in-hand with the authentication. When it comes to authentication in Node.JS, Passport is probably the most common middleware option and offers tons of "strategies" for authentication via Facebook, Twitter, etc.

Coming into the Sails.JS world, there are other options as well including Sails-generate-auth which is an abstraction layer/ middleware that utilizes Passport for authentication. There's also Sails-auth which is also Passport based as well. There's plenty of options out there so it's worth considering the pros and cons of each one.

MachinePacks

Another great option which I've discovered recently which I'll cover in this post is using what's called, the "machine-pack". It's built around the concept of a machine, being that each machine has a well defined purpose and easy to implement (evident of its well written documentation). They also prescribe to a standardized interface which makes it ideal for easy consumption. A set of machine is what constitutes a "machine-pack" which basically combines multiple machines to perform common tasks. A common task can be something like, "authenticate with Facebook" or "send mail with MailGun" and so forth.

A machine-pack that I'd like to share in this post is called machinepack-Facebook which is a machine-pack for authenticating your web application with Facebook. If we explore the machinepack-Facebook bundle itself, it includes a few machines that are stored within the /machines directory which includes get-access-token.js, get-login-url.js, get-longterm-access-token.js and get-user-by-access-token.js.

The concepts in this post are very similar to authentication with Twitter. I've looked everywhere on the Internet for implementation specific to machine-packs and couldn't find one so hopefully this will shed some light on its implementation.

Implementing the machinepack-Facebook

First and foremost, you will need to create an account at Facebook Developers and create an app to get a client/app key and the secret. Once you have those 2 pieces of information, you can create an API by using sails generate api Auth which generates the /api/Auth endpoint. This generates a Model and a Controller API.

Since we're going to use the key and secret multiple times within our controller, I created /config/appsettings.js which is a config that is exported and available throughout the application. It keeps the code clean and configurable. At the top of the controller before the module.exports = { } is where all the declaration resides at.

The first line basically just requires the machinepack-facebook which will need to be installed via npm install machinepack-facebook --save. The rest are convenience variables to eliminate code repetition.

var Facebook = require('machinepack-facebook');

var callbackUrl = sails.config.appsettings.BASE_URL + '/auth/fbcallback',
    fbClientId = sails.config.appsettings.FACEBOOK_CLIENTID,
    fbSecret = sails.config.appsettings.FACEBOOK_SECRET;

The next chunk of code is for the API function which is the entry point to the authentication process. The 2nd line starts builds the login URL with callback along with the permissions that is being required by your application. The "permissions" below corresponds to a set of permission items that you'd like to access and it's in an Array format.

I omitted the error callback/handling for simplicity and to make the concepts clear and to the point. I also suggest refactoring the entire Facebook authentication out as a separate service to keep the controller actions clean.

facebook: function (req, res) {
  Facebook.getLoginUrl({
    appId: fbClientId,
    callbackUrl: callbackUrl,
    permissions: [ 'public_profile' ]
  }).exec({
    error: function (err){ },

    success: function (result){
      return res.redirect(result);
    }
  });
},

In the success callback above, the result is a URL that was formed by the Facebook.getLoginUrl() function. The function below is the callback routine where this will go out to Facebooks's Graph API and asks the user whether it will allow your application to authenticate to Facebook on your behalf. In addition, it also asks for permission(s) such as accessing your profile information, etc.

The getAccessToken() function requires a "code" which is sent back from the previous call to Facebook's auth service. The "code" verifies that the user has permitted your app's access request. getAccessToken's callback then returns a token which can then be used to get the user's information.

fbcallback: function(req, res){
  var code = req.params.all()['code'];

  Facebook.getAccessToken({
    appId: fbClientId,
    appSecret: fbSecret,
    code: code,
    callbackUrl: callbackUrl
  }).exec({
    error: function (err){ },
    success: function (result){
      var token = result.token;

      // Get information about the Facebook user with the specified access token.
      Facebook.getUserByAccessToken({
        accessToken: token
      }).exec({
        error: function (err){ },

        success: function (result){
          // Result will include the user's profile information for consumption.
        }

      });
    }
  });
},

In the end, you can then use the id or the email that was sent back by Facebook to lookup in your database on whether the user exists in which, you will need to create a user, then log the user in automatically.
Using Waterline (ORM in Sails.JS), you can do a query such as (this finds a user by either a facebookId or email which are both valid identifiers):

User.findOne({
  or: [
    { facebookId: result.id },
    { email: result.email }
  ]
}).exec(function(err, user){
  if (user){
    // Update the last logged in date/time stamp and log the user in
  } else {
    // Create a new user and log the user in    
  }
});

As for the routes, we will need to manually modify the /config/routes.js to tell it to point to the correct API endpoint and handle the type of request properly (GET, POST, PUT, DELETE).

'POST /auth/facebook': 'AuthController.facebook',
'POST /auth/fbcallback/:id': 'AuthController.facebookcallback'

I hope that this helps and and shows how easy it is to use the machine-pack with Sails.JS. Please let me know if you have any questions on the comment below.

Comments

comments powered by Disqus