Timi Ajiboye

Sails Authentication with Passport

We've built authentication (for an API) from scratch in this article.

In reality, you'll find that a solution like the one above doesn't quite work.
What if you want to authenticate your users with a sails front-end too, with a session and all that?
Or if you want to sign users up with Twitter, Facebook, Google and the rest?
That's where Passport comes in.

Passport

On the Passport official website, it is described as: "Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more."

Before we move on to talk about Passport in the context of Sails.js, I'll explain a few things about how it works.

Strategies

Strategies are the core of Passport's operation. There are strategies for Twitter authentication, Facebook authentication, Local (username/email & password) Authentication etc.

All these strategies are meant to do one thing for you, create users and authenticate them. What this means is that you get to have one User model that can be created and authenticated in many many different ways. This modularizes things and reduces the complexity at the same time.

passport.authenticate('some-strategy'...  

By implementing Passport, you can authenticate your views or endpoints with code like the one above, employing whichever strategy you like.

Passport in Sails.js

There are many different ways you can implement Passport in your Sails.js application but they generally follow the same path. In fact they're not all that different; a lot of the time, it's just a matter of where you want to put what code. The important parts are as follows:

  • You have a User model that has the necessary attributes for registration and authentication. If you're using a 'local' strategy, this might mean having email and password attributes. If you're also using a 'twitter' strategy, this might mean having a 'twitterId' attribute.
  • You need a controller with actions (that are routed to) to authenticate Users for each strategy.
  • You need to define the strategy...sometimes. A lot of times, you won't need to write custom code for how a strategy should work, except your own LocalStrategy. Other times, it involves extending or overriding things to get them to work a particular custom way.

sails-auth

The above brings me to sails-auth.

It's a very useful node module that makes all the above stress go away.
All you'll need to do would be to extend or modify it's functionality as you wish.

Installation

Important Notice: I have switched to Yarn for dependency management. It is for me, less of a hassle to use than npm (Probably because it's similar to Bundler that I've grown so accustomed to). You can see how to install Yarn here.

You can install sails-auth with:
If you have Yarn installed.

yarn add sails-auth  

or

npm install sails-auth --save  

Usage

Well, that's it. You already have authentication in your sails app. sails-auth automatically creates a User model, an AuthController, policies and protocols behind the scenes to make it all work.
Let's check it out.

sails lift  

You can create users by making POST requests to /register with username, email and password in the body of your request.

There are two strategies that come pre-packaged with sails auth and they already work:

  • Local: This lets you authenticate Users with their username (or email) and password. You can make a POST request to auth/local with identifier (email or username) and password as parameters.
  • Basic: This lets you authenticate API HTTP requests using Basic Auth.

You can see that these strategies have been added in sails-auth/config/passport.js. To use more strategies, you'll need to add them to this file and that brings me to the next topic.

Extending sails-auth

The best way to extend sails-auth models, controllers and config files is by using lodash.

So, for example, you want to add more fields to the User model.

Create a User.js file in your api/models/ folder.

var _ = require('lodash');  
var _super = require('sails-auth/api/models/User');

_.merge(exports, _super)  
_.merge(exports, {

  attributes: {

    first_name: {
      type: "string",
    },

    last_name: {
      type: "string",
    },

    posts: {
      collection: "post",
      via: "author"
    }
  }
})

Lodash's merge function is a super nifty one that merges two objects. In the event that you have clashing/similar keys in your objects, merge will choose the latter. This is perfect for adding stuff to already existing objects and for overriding some of it's functionality.

Another example lies in the passport.js file. Say you want to change the protocol that the local strategy uses or you want to add another strategy.

var _ = require('lodash');  
var _super = require('sails-auth/config/passport');

_.merge(exports, _super)  
_.merge(exports, {  
      local: {
        strategy: require('passport-local').Strategy
        protocol: 'custom-local'
      },

        twitter: {
        strategy: require('passport-twitter').Strategy
      },

})

Protocols

Protocols are a part of sails-auth that you don't need to understand to get going. But a little knowledge of how it all works can't hurt.

According to the sails-auth source code documentation/comments:
"Protocols where introduced to patch all the little inconsistencies between the different authentication APIs. While the local authentication strategy is as straight-forward as it gets, there are some differences between the services that expose an API for authentication."

Basically, they expose certain functions that the sails-auth passport service uses to create and authenticate users. Therefore by specifying a protocol, when adding a strategy, you're essentially telling sails-auth how to act when using the strategy.

That way you can tell sails-auth to either oauth or oauth2 with your twitter strategy.

Or you can roll your own protocol and tell it to use that instead.

That’s all for today folks.

O dabọ.

Update (1st December 2016)

So I decided to use this in an actual API I'm working on, using the Basic Strategy.
I pushed the application to Heroku (post on this coming soon) and for some reason I couldn't get an Authenticated request to work more than once.
I kept getting a "Failed to deserialize User out of session" error. After two days of fighting, I finally stumbled on a workaround that was inspired by this issue (an issue I had seen no less than a 100 times while trying to stumble upon a solution).

Anyway, here's the fix so you don't have to suffer like I did.

You need to override the passport.js service that comes with sails-auth. More specifically, the deserializeUser function.

Here's the code that made all my sails-auth production problems go away.

var _ = require('lodash');  
var _super = require('sails-auth/dist/api/services/passport');  
var passport = require('passport');

passport.deserializeUser(function (id, done) {  
  done(null, false);
});

_.merge(exports, _super);  
_.merge(exports, passport)