OAuth2 with Waterlock

Posted by n3integration on June 10, 2015

I’ve spent the last few weeks developing an application using Sails.js. Being a side-project, my development time is limited and Sails has been great in allowing me to get a prototype up and running quickly.

The application requires that a user is authenticated and authorized to perform certain functionality (i.e. admin vs read-only). My hosting requirements don’t allow for SSL, and providing BASIC authentication over HTTP is not an option. Although I have never had a need to use OAuth in the past, since I spend most time developing applications where certificates are available or enterprise authentication (i.e. LDAP/ActiveDirectory) is preferred, this seemed appropriate for the application’s requirements.

After a quick Google search for libraries compatible with Sails, I came across Waterlock. Waterlock provides three authentication methods that use JWT, or Json Web Tokens, to manage user authentication:

  1. Local Auth
  2. Facebook Auth
  3. Twitter Auth

Local Auth allows the application to manage users using the application’s datastore and comes with user registration capabilities built-in. Although I could have taken the Local Auth route, who wants to manage another username and password combination? Next, I figured, “Great! I am pretty sure the users have Facebook accounts.”, so I went the Facebook Auth route.

Waterlock is installed using npm.

$ npm install --save waterlock

After installing Waterlock, the basic scaffolding (i.e. configuration files, controllers, models) need to be generated.

$ ./node_modules/waterlock/bin/waterlock.js generate all

Since Waterlock is a modular authentication framework, the authentication modules must be installed separately using npm.

$ npm install --save waterlock-facebook-auth

Once the Waterlock files are generated and the authentication module is installed, the config/waterlock.js file needs to be updated. My updates are included below:

  baseUrl: process.env.BASE_URL || 'http://localhost:1337',
  authMethod: [
    {
       name: "waterlock-facebook-auth",
       appId: process.env.APP_ID,
       appSecret: process.env.APP_SECRET
    }
  ],
  jsonWebTokens:{
    secret: process.env.JWT_SECRET,
    expiry:{
      unit: 'hours',
      length: '1'
    },
    audience: process.env.APP_NAME,
    subject: 'subject',
    trackUsage: true,
    stateless: false,
    tokenProperty: 'token',
    expiresProperty: 'expires',
    includeUserInJwtResponse: true
  },
  postActions: {
    login: {
      success: '/#/main',
      failure: 'default'
    }
    logout: {
      success: '/#/login'
      failure: 'default'
    }  
  }

In order for the client application to authenticate through Facebook, the client application needs a link set to /auth/login. This redirects the client to the Facebook login screen where the user enters their credentials. Once successfully logged in, the user is redirected back to the client application. In the above case, the user is redirected to the page rendered by the /#/main route. Let’s fire up the app and give it a shot.

$ sails lift

Not bad. With minimal effort, we’ve integrated authentication using an upstream service provider. Just one small problem…it turns out that not all of the application users have a Facebook account.

So how about Gmail accounts? I didn’t recall seeing Google in the official list of available authentication providers. However, a quick search in GitHub returned the waterlock-google-auth project. The installation and configuration appeared straightforward. First, I removed the waterlock-facebook-auth dependency from my package.json file. Then, I installed the waterlock-google-auth module.

$ npm install --save waterlock-google-auth

The config/waterlock.js configuration was slightly different also:

  authMethod: [
    {
       name: "waterlock-google-auth",
       clientId: process.env.CLIENT_ID,
       clientSecret: process.env.CLIENT_SECRET
    }
  ],

In order to secure access to the resources that require an authenticated user, the config/policies.js file needs to be updated. To set this up, the controller name, along with the list of routes must be defined in this file. In the following example, all routes in the ContactController require an authenticated session. Otherwise, an error with a 403 status code is returned to the client.

  ContactController: {
    '*': ['sessionAuth']
  },

With this setting in place, let’s fire up the app again and give it another shot.

$ sails lift

Nice! A quick and painless switch from Facebook to Google authentication and user management is hosted outside of the application.