Security Measures

As we well know the web has several flaws and security issues which we try to protect against every day. Among them we have XSS (Cross-site scripting), Session Hijack and Cross-Origin Resource Sharing. In short sentences we can describe each of them as:

  • XSS: Cross-Site Scripting is when a user tries to inject executable scripts into your site to try to get sensitive data, do malicious actions into you domain or into other users computers. This kind of security breach is due to lack of validation by the application in question;
  • Session Hijacking: is when a cracker steals an user's session to pretend to be him to do malicous actions or steal sensitive data. It can be done using XSS, a Malware, fixation or sidejacking. It consists of an attacker getting the user's cookie/ authentication token an use it to access the service/application;
  • CORS: Cross-Origin Resource Sharing standard is a set of headers that grant different domains access to a different domain than which they are from. For instance, a website at http://domain.com links a image from http://domainX.com, and the last domain allows for external clients get its resources.

So, every time we build a web application we must be aware of these flaws and fight against them, as hard as we can! Now is the time to ask: André, how can we fight against them? No rush, take your time to ask, don't be shy, I'm very friendly.

Good question! How? Well, that is not a easy task, but it is feasible. First of all you need to do in order to defend yourself is to take some self-defense lessons, kung-fu and jiu jitsu are good choises... ops! Wrong class!

One more time... How? Well, you can start making your backend fool-proof! And to do that you should always validate all client's/requester's input data. You can even go one step further and validate your output value after processing a request.

Easier said than done! Well, that is not totally true. It has always been a good practice to validate all input data in the controllers of an API. Create well defined business rules helps a lot in this kind of situations. So you might consider creating regular expressions for all of your inputs.

You might be asking about how can you validate your output. For pure services, which do not provide webpages, it is easier to check this, as you can guess is just to validate your data. However, for HTML it might be trickier, you might have to escape dangerous HTML and such. The good news is that some HTML templating framework already do this kind of work for you, for example Handlebars. All variables added to the template inside the double brackets {{}} are escaped.

Other useful tool to make it even harder to crackers to do something malicious into your application is to create session restrictions and mechanism such authentication tokens (ex: cookies). Those tokens can be ruled by a set of guidelines which will give some headache to break. Some of these rules are:

  • Authentication cookie;
  • Session expiration time;
  • Application under HTTPS;
  • Persisted client session in backend;
  • Encrypted information;
  • Encrypted authentication token;
  • Encrypted network;
  • Authorization layer on backend.

This is all that I can remember by heart. But what we've learnt so far is that we can use tokens to make our application more secure and validate client's input to make it even safer and robust.

Cross-Origin Resource Sharing

Lets start with something easy to solve, CORS. Imagine that you have a bunch of different APIs up and running in different places. One day you had the big idea to create another SPA, however you decided to reuse some endpoints from another API which you have, because it felt right. So, as a smart boy as you are, you dicided to create a server just to serve your new SPA application. However, while you were developing your SPA, for some reason, you couldn´t do any AJAX.

Well, this happens to protect any server from running any harmful or unauthorized action into its core. So enabling CORS to a specific amount of domains is a secure way to white list which domain is secure to receive actions from. So now it is a good moment to show you guys how to do this in Node. As Express is all about middlewares, why not creating one?

'use strict';

const allowedMethods = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS'];
const allowedHeaders = ['Content-Type', 'Authorization', 'Content-Length'];

const CORSEnabler = function (request, response, next) {
  console.log('Configuring CORS');
  console.log(`Method ${request.method} from ${request.origin}`);

  if (allowedMethod.indexOf(request.method) > -1) {
    response.header('Access-Control-Allow-Origin', '*');
    response.header('Access-Control-Allow-Methods', allowedMethods.join(','));
    response.header('Access-Control-Allow-Headers', allowedHeaders.join(', '));
  }

  next();
}

module.exports = CORSEnabler;
CORS Middleware

Now we add it as a middleware in our API.

'use strict';

const express = require('express');
const dogRoute = require('./dogRoute');
const goodDogRoute = require('./goodDogRoute');
const wiggleRoute = require('./wiggleRoute');
const CORS = require('./CORSEnabler');
const app = express();

app.use(CORS);

app.use('/dog', dogRoute);
app.use('/goodDog', goodDogRoute);
app.use('/wiggle', wiggleRoute);

app.listen(8080, function () {
  console.log('Listening on port 8080...');
});
Server with CORS configuration

As this CORS configuration is a middleware we can use it in all the ways we now it can be used, like enabling CORS for just a specific route or a specific endpoint.

Cookie Authentication

Hello guys, how you doing? I'm feeling great! So great that I want to do some free style coding right now. As I said in the first lecture, Node is low level. You might be wandering about how secure Node is, how secure my application is right now. I can tell you, it is not secure.

You could make calls from any client, from any browser with any kind of info. Node and Express do not provide to us any security layer. So it is our job to add one (it is fun!). One popular way that APIs are implementing is token based authentication.

Token Based Authentication

Why it is so popular? Well, with this concept you can have your RESTful service stay stateless however with the ability to know if the requester is trustable or not, using a public network. The core concept is simple: Our API will provide to the requester a token. This token is responsible to define if the requester is trustable or not. We do this by checking the correctness of this token. As the API is providing this little guy we can encrypt information using our favorite algorithm with our hard-to-decode secret that will be private. This way no one can fake a token (more or less, there are ways but we are trying to make it as hard as possible, not impossible).

Interesting, isn't it? What I want to teach you guys about is Json Web Token. Please read this article before continuing, because I will assume that you know what a JWT is.

After getting our fact right here is what I want to tell you: I will create a token using JWT concept. This token will be stored in a cookie where I find a safe place to be stored. This can give me the feature of session in my application and security if I'm in a HTTPS environment.

After defining this my application can now search for this cookie to do our authentication feature. I will add a middleware into my Dog Service that will be executed before any route that I find reasonable to have it. This middleware will get the token from the request's cookie, verify it and then decide if the request should be authorized or not. For that I will add two packages from NPM.

The Middleware

I've already told you what I'm about to code and I don't want to be redundant, so lets get directly to what matters. Create a auth.js file with the content below.

'use strict';

const jwt = require('jsonwebtoken');
const cookieName = 'dogServiceCookie';
const secret = 'HanSoloShootsFirst';
const issuer = 'Peter Griffin';
const twoMinutes = '2';

const Auth = function() {
  const verifyAuth = function(request) {
    let cookie = request.cookies[cookieName];
    if (cookie) {
      try {
        request.token = jwt.verify(cookie,
                                   secret,
                                   {issuer: issuer,
                                    ignoreExpiration: false});
        return true;
      } catch (error) {
        console.log('Request unauthorized. Error decoding token.');
        return false;
      }
    } else {
      console.log('Request unauthorized. No token available.');
      return false;
    }
  };

  this.isAuthenticated = function (request, response, next) {

    if (verifyAuth(request)) {
      next();
    } else {
      response.sendStatus(407);
    }
  };

  this.signToken = function (payload) {
    return jwt.sign(payload, secret, {issuer: issuer,
                                      expiresInMinutes: twoMinutes});
  };
};

module.exports = new Auth();
Cookie Authentication Middleware
Cookie Middleware ES6 Class
Cookie Middleware Object Composition

Now I'm going to add these middleware in the Dog Service code. I will add it in the dog route since it has the DELETE, POST and PUT methods.

'use strict';

const bodyParser = require('body-parser');
const parseUrlencoded = bodyParser.urlencoded({extend: false});
const express = require('express');
const router = express.Router();
const Dog = require('./Dog');
let dogs = require('./dogs');

router.use(auth.isAuthenticated);
router.use(parseUrlencoded);

router.route('/')
  .post(function(request, response){
    const data = request.body;

    if (data) {
      const newDog = Dog(data.name);

      dogs.push(newDog);

      response.status(201).send(`Dog ${data.name} added! Brian loved it.`);
    } else {
      response.sendStatus(403);
    }
  })
  .put(function (request, response) {
    const data = request.body;

    if (data && data.index && data.name){
      const dog = dogs[data.index];
      dog.setName(data.name);

      response.send(`Dog #${data.index} had its name changed to ${dog.getName()}`);
    } else {
      response.sendStatus(403);
    }
  });

router.route('/:doggyId')
  .delete(function(request, response){
    const doggyId = request.params.doggyId;

    if (doggyId && doggyId < dogs.length) {
      const removed = dogs.splice(doggyId);

      response.send(`Dog #${doggyId} was removed. Now we have ${dogs.length} dogs.`);
    } else {
      response.sendStatus(403);
    }
  });

module.exports = router;
Dog Service With Authentication

Now I will create a login route.

'use strict';

const express = require('express');
const router = express.Router();
const auth = require('./auth');
const bodyParser = require('body-parser');
const parseUrlencoded = bodyParser.urlencoded({extended: false});
let dogs = require('./dogs');

router.route('/')
    .post(parseUrlencoded, function(request, response){
        let data = request.body;

        if (data && data.doggyId && data.doggyId < dogs.length) {
            let dog = dogs[data.doggyId];
            let payload = {name: dog.getName(), id: data.doggyId};
            const token = auth.signToken(payload);

            // set the cookie on the response
            response.cookie("dogServiceCookie" ,token);
            response.status(200).json(token);
        }else{
            response.sendStatus(403);
        }
    });

module.exports = router;
loginRoute.js
'use strict';

const express = require('express');
const cookieParser = require('cookie-parser');
const dogRoute = require('./dogRoute');
const goodDogRoute = require('./goodDogRoute');
const wiggleRoute = require('./wiggleRoute');
const loginRoute = require('./loginRoute');
const app = express();

app.use(cookieParser());
app.use('/dog', dogRoute);
app.use('/goodDog', goodDogRoute);
app.use('/wiggle', wiggleRoute);
app.use('/login', loginRoute);

app.listen(8080, function(){
    console.log('Listening on port 8080...');
});
Adding Login Route To Our Application

That is all it takes. Now we can test it to see if our operations over Dog Services are working. First of all do a POST call to http://localhost:8080/login , with a doggyId param. It will create a auth cookie on your response, which you can use on the next calls to http://localhost:8080/dog .

Exercises

Keep using your code which you developed on last lecture

Exercise 1:

Now that we have all endpoints done, lets create a login feature. First create a POST login route, where it receives an email and a password. This route will get this information, try to get the user from the database and create a cookie to be stored in the client. The content of this cookie is an object which contains the attribute logged that receives a boolean value. You have already created a cookie middleware in the last exercices, refactor it to get the auth cookie and authorize the client to use this routes:

  • GET: retrive a specific user;
  • DELETE: remove a user;
  • PUT: change user's password.
Don't forget to handle errors, problems and create a log out route.

Exercise 2:

Create a JWT middleware to add another security layer to your APP. User JWT standard in your auth cookie created in the last exercise. It should expire in one hour. Your payload should have this attributes:

  • email;
  • admin;
  • issuer.

Exercise 3:

Create in your HTML providaded by your exercise's repository a section to log in into your application.

Exercise 4:

Using the same code from last exercise create a new route that get the info from the logged user. Your route should be http://localhost:3000/me

Exercise 5:

Add these new rules into your API:

  • Delete user should require authentication and the logged user should be an Admin;
  • Change user's password should require authentication and should only be done by the same user;

Exercise 6:

Enable CORS in your application for any domain, making any action and having any headers. Remember to have a config file for that. You can use what you already have.

Exercise 7:

Create a white list of domains which can access your domain. And make it only this endpoints available:

  • Get all users;
  • Log in;
  • Get info from logged user.

Exercise 8:

Revise your code. Try to see if there is any security issues. Validate all your incoming data. Make your API robust.

Exercise 9:

CHALLENGE: Using the same code from last exercise implement complete new set of routes for friendship. You should creates all routes and the friendshipDAO (you should use the same concept applied to userDAO). You should implement this routes:

  • http://localhost:3000/friendships/ - GET - list all friendships - Require Authentication;
  • http://localhost:3000/friendships/me - GET - list logged user friendships - Require Authentication;
  • http://localhost:3000/friendships/:friendId - GET - get friendship with a user - Require Authentication;
  • http://localhost:3000/friendships/:friendId - POST - send friend request to a user - Require Authentication;
  • http://localhost:3000/friendships/:friendId - PUT - accept friend request - Require Authentication;
  • http://localhost:3000/friendships/:friendId - delete - reject friend request - Require Authentication;
  • http://localhost:3000/friendships/:friendId/block - GET - block user - Require Authentication;
  • http://localhost:3000/friendships/:friendId/block - GET - unblock user - Require Authentication;

Your friendshipDao should have this methods:

  • getFriendship = function(id, succesCB, failCB)
  • getFriendshipByUserId = function(userId, friendId, successCB, failCB)
  • listAllFriendships = function(successCB, failCB)
  • listAllMyFriendships = function(userId, successCB, failCB)
  • createFriendship = function(userId, friendId, successCB, failCB)
  • updateFriendshipProperty = function(userId, friendId, property, value, successCB, failCB)
  • updateFriendshipPropertyById = function(id, property, value, successCB, failCB)
  • updateFriendshipStatus = function(id, status, successCB, failCB)

Your friendship model should have this attributes:

  • status: number
  • userRequested: id
  • userRequester: id
  • blockUserRequested: id
  • blockUserRequester: id

Exercise 10:

CHALLENGE: search for 'CORS preflight' and implement it in your application.