{ Authentication with JSON APIs. }

Objectives:

By the end of this chapter, you should be able to:

  • Describe how to secure an API
  • Understand what a JWT is and how to create one
  • Authenticate an API using JSON Web Tokens

Authenticating an API

So how do we secure our API? We can do what we have been doing before and store information in the session and in cookies, but this can become problematic. This is especially true when you're working with lots of different servers (lots of small applications written in different languages) or when you're building mobile applications, which have severe limitations with cookies.

Instead of using cookies/sessions to authenticate users, we will be using tokens. This process involves creating an encrypted token on the server and sending it to the client where it will either be stored in a cookie or in localStorage (more commonly done with localStorage), and on every future request, the token will be placed in the header and will be decrypted on the server.

The server will store a secret key used to encrypt and decrypt the token so that there can not be tampering with the token in the client.

You can read more about tokens versus cookies here.

The type of token that we will be using is a JSON Web Token, or JWT (pronounced "jot").

Using JWTs

Before we continue, read through this introduction to JWTs so you can understand a bit more about what they are and how they work. JWTs are a means of securing information between two parties and consist of three parts:

  • Header - metadata about the token (the type of algorithm used to sign and the type of token)
  • Payload - data to be stored in the token (an object with the data we want to store like a user id)
  • Signature - the result of the algorithm specified in the header (we will be using HMAC-SHA256) with an encoded header, encoded payload, and a secret passed to it.

We can use passport-jwt for this, but let's abstract a little bit less and roll our own authentication with JSON web tokens!

Example App

The module we will be using to create secure tokens is jsonwebtoken.

We will start with a simple application here:

mkdir learn_jwt && cd learn_jwt
touch app.js
npm init -y 
npm install --save express body-parser morgan mongoose jsonwebtoken 

We will keep things simple and place all of our code in an app.js for this example:

var express = require("express");
var app = express();
var morgan = require("morgan");
var bodyParser = require("body-parser");
var jwt = require("jsonwebtoken");
var bcrypt = require("bcrypt")

app.use(morgan("tiny"))

app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());

var mongoose = require("mongoose");
mongoose.set("debug", true);
mongoose.connect("mongodb://localhost/learn_jwt");
mongoose.Promise = global.Promise;

userSchema = new mongoose.Schema({ 
    name: String, 
    password: String,
});

userSchema.pre("save", function(next){
  var user = this;

  if (!user.isModified("password")) return next();

  bcrypt.hash(user.password, 10).then(function(hashedPassword) {
    user.password = hashedPassword;
    next();
  }, function(err){
    return next(err);
  });
});

userSchema.methods.comparePassword = function(candidatePassword, next) {
  bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
    if (err) return next(err);
    next(null, isMatch);
  });
};

// set up a mongoose model 
var User = mongoose.model('User', userSchema);

// you MUST put this in a .env file
var secret = "NEVER EVER MAKE THIS PUBLIC!";

app.post('/signup', function(req,res) {
  User.create(req.body).then(function(user){
    res.status(201).send(user);
  });
});

app.post('/authenticate', function(req, res, next) {
  // find a user by their name
  User.findOne({name: req.body.name})
  .then(function(user) {
    // if there is no user
    console.log("USER", user)
    if (!user) {
      res.status(400).send({ message: 'Invalid Credentials' });
    }
    user.comparePassword(req.body.password, function(err, isMatch) {
      if(isMatch){
        var token = jwt.sign({name: user.name}, secret, {
          expiresIn: 60 * 60 // expire in one hour
        });
        res.send({message: 'Authenticated!',
          token: token
        });
      } else {
        res.status(400).send({ message: 'Invalid Credentials' });
      }
    });
  }, function(err) {
    console.log("ERRORS!", err);
  });
});

app.use(function(req, res, next) {
  // check header or url parameters or post parameters for token
  var token = req.query.token || req.body.token || req.headers['x-access-token'];
  if (token) {
    jwt.verify(token, secret, function(err, decoded) {      
      if (err) return res.send({ message: 'Invalid Token'});    
      else {
        req.decoded = decoded;    
        next();
      }
    });
  } else {
    return res.status(403).send({ message: 'No token provided.' });
  }
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.send({
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.send({
    message: err.message,
    error: {}
  });
});

app.listen(3000, function(){
  console.log("Server is listening on port 3000");
});

Enabling CORS

When setting up a Node.js API on its own (without any kind of front-end), we will be accessing the API most likely from a different domain. Since the same origin policy enforces that we can not run JavaScript on another domain, we need to enable our API to bypass that security policy through allowing certain origins (domains).

Cross Origin Resource Sharing (or CORS) is one way of doing this. By adding specific headers on the server, we can enable browsers to execute JavaScript on the domain that the API is hosted on. To enable CORS, we can either manually add the headers ourselves, or use the helpful CORS module.

Sample application

You can see a sample application with JSON API Authentication here.

When you're ready, move on to Testing JSON APIs

Continue