Timi Ajiboye

Building API Authentication from Scratch - Part 1

Since the beginning of hellosails.com, there have been articles on the following topics:

The above should provide enough knowledge to understand how to build something (relatively small) with a real use case.

So today, we're gonna build simple authentication for our Sails API from scratch.

There are other tools one can use to (greatly) ease the process of implementing authentication like Passport & Auth0, but it's nice to know how to build these things.

You can find all the code for this part in the series on GitHub here.

What we're trying to build

Our application shall have a User model with a name, an email and a password. There'll be a Post endpoint to create a User and this endpoint would be unauthenticated; anybody should be able create a User.
Also, we'll need to create another POST endpoint to sign the User in. This endpoint will verify that the user exists in the database and that the password is correct, and it shall respond with the User object and her/his authentication token.

This token is used to authenticate further requests. The token must be present in the header of any request that our API receives, in the form Authorization: Bearer token.

Lastly, we'll have another endpoint that lets a User edit it's profile.

Requirements

  • Plain password should not be saved in the database.
  • Password should be encrypted.
  • token should expire after some time, forcing the User to login again.
  • The endpoint that lets a User edit it's profile must be authenticated.
  • This endpoint must also not let a User edit another User.

The above is a (non-exhaustive) list of some of the things that need to be present in an authentication system.

Let's Code

Create a new API only Sails.js app using sails new auth-example --no-frontend.

Also, let us turn off blueprints for this example. In /config/blueprints.js

module.exports.blueprints = {  
  rest: false,
  actions: false,
  shortcuts: false
};

The User Model

Let’s create a User model.

sails generate model User  

In our /models/user.js file, we need to add the following attributes and validations.

module.exports = {  
  attributes: {
    email: {
      type: "email",
      required: true,
      unique: true
    },
    password:{
      type: "string",
      minLength: 6,
      protected: true,
      required: true,
      columnName: "encryptedPassword"
    },  
    toJSON: function(){
        var obj = this.toObject()
        delete obj.password
    }
    }
};

The above code is pretty straightforward except for a couple of things:
- columnName: "encryptedPassword". Remember our requirements? We’re not meant to save the actual password to the database but an encrypted version. What that line of code does is to specify that the column name in the database for that particular attribute should be encryptedPassword and not the default password. As for actually encrypting the password and saving that to the database, we’ll be getting to that soon. - toJSON: Here we’re overriding the function that converts our model into a JSON response and deleting the password from that object. We don’t want the password (encrypted or not) to show up in our responses.

To handle encryption, we’ll make use of a library called bcrypt. You can read more about it here.

npm install bcrypt  

Add var bcrypt = require("bcrypt") to the top of your user.js file.
Now, We’re going to make use of Sails model Lifecycle callbacks. “Lifecycle callbacks are functions that are automagically called before or after certain model actions.”

var bcrypt = require("bcrypt")  
module.exports = {  
    attributes: {
        … 
    }
    … 
    beforeCreate: function(values, cb){
        bcrypt.hash(values.password, 10, function (err, hash) {
      if (err) return cb(err);
      values.password = hash;
      cb();
    });
    }
}

The above code simply creates an encrypted hash from the actual password and replaces it.

“Calling cb() with an argument returns an error. Useful for canceling the entire operation if some criteria fails.”

To round up this User model, we’re going to create one more fucntion called comparePassword.
It shall be a Promisified function that will resolve when the password matches the argument passed and reject when it doesn’t.
Bluebird is the Promise library we’ll be using and to do that, we need to npm install bluebird.

var bcrypt = require("bcrypt")  
var Promise = require("bluebird")

module.exports = {  
    attributes: {
        … 
    }
    … 
    beforeCreate: function(values, cb){
        …
    });
    },

    comparePassword: function(password, user) {
        return new Promise(function (resolve, reject) {
      bcrypt.compare(password, user.password, function (err, match) {
        if (err) reject(err);

        if (match) {
          resolve(true);
        } else {
          reject(err);
        }
      })
    });
    }
}

JSON Web Token

According to jwt.io -
“JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.”

To elucidate, a JSON Web Token is a string:
1. That carries all information within itself. This means that when a JWT is decoded, the result is a JSON Object with all the information that needs to be transferred.
2. That can be used to verify the sent data was actually created by an authentic source.

From the above, it’s clear that JWTs are perfect authenticating a user.

To make use of JWTs, we are going to create a simple Service that abstracts the functionality we want. Thankfully, this is easy, thanks to the jsonwebtoken node module.

npm install jsonwebtoken  

We need to create a JwtService.js in the api/services folder.

var jwt = require('jsonwebtoken');  
var jwtSecret = sails.config.secrets.jwtSecret;

module.exports = {  
  issue: function (payload) {
    token = jwt.sign(payload, jwtSecret, {expiresIn: 180 * 60})
    return token
  },

  verify: function (token, callback) {
    return jwt.verify(token, jwtSecret, callback);
  }
}

Create a file /config/secrets.js, and in it, we add our jwtSecret.

module.exports.secrets = {  
  jwtSecret: process.env.TOKEN_SECRET || "randomstring"
};

It is important to note that it is bad practice to include your secret in your code and hence; version control. Except it’s a development secret (that your collaborators can use to test), then you can do exactly what’s in the code above.
In production, we would have an environment variable TOKEN_SECRET that store the actual production secret.

Sign Up

Now, we create a UserController.js file in the controllers folder.

In it, we’re going to have one action that creates a user.

var _ = require('lodash');  
module.exports = {  
  create: function (req, res) {
    if (req.body.password !== req.body.confirmPassword) {
      return ResponseService.json(401, res, "Password doesn't match")
    }

    var allowedParameters = [
      "email", "password"
    ]

    var data = _.pick(req.body, allowedParameters);

    User.create(data).then(function (user) {
      var responseData = {
        user: user,
        token: JwtService.issue({id: user.id})
      }
      return ResponseService.json(200, res, "User created successfully", responseData)
    }).catch(function (error) {
        if (error.invalidAttributes){
          return ResponseService.json(400, res, "User could not be created", error.Errors)
        }
      }
    )

  }
};

Things to note about the above code:

  • We used lodash to manipulate arrays, so you’ll need to npm install lodash first.
  • We used a certain ResponseService to format our request responses. There’s a previous post that’s short which explains how to write this service.
  • The rest is pretty straightforward, we first check if the confirmPassword parameter matches the password. Then we strip the request parameters of anything that isn’t in allowedParameters. We then create a User using Sails.js/Waterline queries and their in-built Promise support. Finally we issue a JWT and add it to the response object as token.

To complete the sign up flow, we add a route to /config/routes.js/

module.exports.routes = {  
  'post /signup': 'UserController.create'
};

This simply means that any POST request to the /signup endpoint will execute the create function we just made.

To test, fire up your Sails app with sails lift and send a POST request with Postman to see if your User gets created.

You should get a response like this

{
  "response": {
    "message": "User created successfully",
    "data": {
      "user": {
        "email": "[email protected]",
        "createdAt": "2016-10-07T14:49:17.329Z",
        "updatedAt": "2016-10-07T14:49:17.329Z",
        "id": 11
      },
      "token": "really.long.jsonwebtoken"
    }
  }
}

That’s all for this part of this post.
In the next post we shall proceed to create our /login endpoint and the policy we’ll use to authenticate further requests.

Remember, you can find all the code for this part in the series on GitHub here.