Skip to main content

Secure your Node/Express REST APIs using Passport JS

Recently I have been involved in various discussions on how to make the REST APIs as secure as SOAP.
First of all, let me start with a very basic statement, about security, it doesn't depend on your Webservice type, be it REST or SOAP, your design decisions depicts whether they can be made secure or not.
In this example blog, I will use "Micro CRUD services for Oracle Database Cloud" APIs and implement (read attach) Passport's "local" authentication strategy, to make them secure.

Code in Github : LeasifyAPIs with Passport

What is Passport JS? (from Documentation)

Passport is authentication middleware for Node. It is designed to serve a singular purpose: authenticate requests. When writing modules, encapsulation is a virtue, so Passport delegates all other functionality to the application. This separation of concerns keeps code clean and maintainable, and makes Passport extremely easy to integrate into an application.

What are "strategies" in Passport JS? 

"Strategies" are different techniques to authenticate an user, who is accessing the hosted REST APIs. Be it, authenticating using LDAPs, Identity stores, OAuth, social (Facebook, Google or LinkedIn) etc. Passport provides around 307 different strategies to choose from. Check out their website for more info : http://passportjs.org/

What is it I'm going to "attach"?

I mentioned "attach", not implement. Because, I am going to :
  • Add 2 Node Modules
  • Initialize passport for a "strategy"
  • Add a /login API for users to authenticate
  • Add one extra parameter to the existing API definitions to make them secure.
So basically, the existing APIs can be made secure just by "attaching" one extra parameter.

Authentication mechanism

Let me explain, my authentication mechanism a bit.

"salt" and "hash"
I am going to authenticate the users, from database. I have a table containing all the users. But I don't store passwords, instead, I store "salt" and "hash".
The "salt" is a string of characters unique to each user.
The "hash" is created by combining the password provided by the user and the salt, and then applying one-way encryption.
As the hash cannot be decrypted, the only way to authenticate a user is to take the password, combine it with the salt and encrypt it again. If the output of this matches the hash, then the password must have been correct. Resulting in a simple yet very secure implementation.

JSON Web Token
Once, I authenticate the users, via an API, I return a JSON Web Token. Which has a validity of 7 days, and needs to used in the header for all other API calls.

What is JSON Web Token, aka JWT? 
Instead of supplying credentials such as a username and password with every request, we can allow the client to exchange valid credentials for a token. This token gives the client access to resources on the server. Tokens are generally much longer and more obfuscated than a password. For example, the JWTs we’re going to be dealing with are on the order of ~150 characters. Once the token is obtained, it must be sent with every API call.
Think of the token like a security pass. You identify yourself at the front desk of a restricted building on arrival (supply your username and password), and if you can be successfully identified you’re issued a security pass. As you move around the building (attempt to access resources by making calls to the API) you are required to show your pass, rather than go through the initial identification process all over again.

So my implementation consists of these 3 major parts :
  1. /addUser : API which adds a new user to the database. This API is also responsible to generates salt and hash based on the password provided, and stores it in the DB.
  2. DB Table : Users, with 3 important columns. Email (my username), salt and hash.
  3.  /login : API which takes email and password and returns JSON Web Token, this needs to be in the header for all the calls to secured APIs.
Code examples 
users.js
A simple JS model for user object.
Some of the important implementation : "setPassword"

var crypto = require('crypto');

User.prototype.setPassword = function(password){
  this.salt = crypto.randomBytes(16).toString('hex');
  this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};


"validate password" :
User.prototype.validPassword = function(password) {
  var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
  return this.hash === hash;
};


Nothing needs installing as crypto ships as part of Node. Crypto itself has several methods; we’re interested in randomBytes to create the random salt and pbkdf2Sync to create the hash (there’s much more about Crypto in the Node.js API docs).

"generate JWT" :
var jwt = require('jsonwebtoken');

User.prototype.generateJwt = function() {
  var expiry = new Date();
  expiry.setDate(expiry.getDate() + 7);

  return jwt.sign({
    _id: this._id,
    email: this.email,
    name: this.name,
    exp: parseInt(expiry.getTime() / 1000),
  }, "MY_SECRET");
};


To create the JWT we’ll use a module called jsonwebtoken which needs to be installed in the application (use npm install for this).
This module exposes a sign method that we can use to create a JWT, simply passing it the data we want to include in the token, plus a secret that the hashing algorithm will use. The data should be sent as a JavaScript object, and include an expiry date in an exp property.
It is important that your secret ("MY_SECRET") is kept safe – only the originating server should know what it is. It is best practice to set the secret as an environment variable, and not have it in the source code, especially if your code is stored in version control somewhere.

passport configurations 

To use Passport, first install it and the strategy, saving them in package.json.

npm install passport --save
npm install passport-local --save

create a file "passport.js" and put the following code

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('./users');
var oracledb = require('oracledb');
var dbutil = require('../dbutil');

passport.use(new LocalStrategy({
    usernameField: 'email'
  },
  function(username, password, done) {
     
      dbutil.executeDatabaseOperation(function(error) {
          return done(err);
      }, function(connection) {
          var selectStatement = "SELECT NAME, EMAIL, HASH, SALT FROM USERS WHERE EMAIL= :userEmail";
          connection.execute(   selectStatement  
            , [username], {outFormat: oracledb.OBJECT // Return the result as Object
            }, function (err, result) {
                if (err) {
                  console.log('Error in execution of select statement'+err.message);
                  return done(err); 
                } else {
                   console.log('db response is ready '+result.rows);
                   if(result.rows.length === 0) {
                       return done(null, false, {
                          message: 'User not found'
                       });
                   }
                   dbutil.doRelease(connection);
                  
                   var user = new User(result.rows[0].NAME, result.rows[0].EMAIL);   
                   user.setHash(result.rows[0].HASH);
                   user.setSalt(result.rows[0].SALT);
                    // Return if password is wrong
                   if (!user.validPassword(password)) {
                    return done(null, false, {
                      message: 'Password is wrong'
                    });
                   }
                   // If credentials are correct, return the user object
                   return done(null, user);
                }               
              }
          );
      });   
  }
));


The above code, will retrieve the "user" from database using the email, and then will validate the password passed to this function, eventually return the user object (which we'll use a little bit late to return the JWT).

So, now I have a implemented the "local" strategy to validate a username and password. Time to write an "authenticator" which will use this strategy to validate users.

authentication.js

var passport = require('passport');
var User = require('./users');

var sendJSONresponse = function(res, status, content) {
  res.status(status);
  res.json(content);
};

module.exports.login = function(req, res) {

  if(!req.body.email || !req.body.password) {
     sendJSONresponse(res, 400, {
       "message": "All fields required"
     });
     return;
  }

  passport.authenticate('local', function(err, user, info){
    var token;

    // If Passport throws/catches an error
    if (err) {
        console.log(err);
      res.status(404).json(err);
      return;
    }

    // If a user is found
    if(user){
      token = user.generateJwt();
      res.status(200);
      res.json({
        "token" : token
      });
    } else {
        console.log(info);
      // If user is not found
      res.status(401).json(info);
    }
  })(req, res);

};


Now I have to link this to /login endpoint. So, in "/routes/index.js", I will add the following :

var ctrlAuth = require('./authenticate/authentication');

router.post('/login', ctrlAuth.login);


The "login" will return the JWT in the response. Time to secure rest of the REST apis.

First initialize passport in "app.js"

var passport = require('passport');
require('./routes/authenticate/passport');


app.use(passport.initialize());

Then, in "/routes/index.js", initialize JWT module. 

var jwt = require('express-jwt');
var auth = jwt({
  secret: 'MY_SECRET',
  userProperty: 'payload'
});


Again, don’t keep the secret in the code! Even though I did :P

To apply this middleware simply reference the function in the middle of the route to be protected, like this: 
router.get('/api/getUser', auth, function(req, res, next)...

So, the endpoints which were defined earlier as :
router.<method>('/endpoint', function(req, res, next) {})...

Will be now :
router.<method>('/endpoint', auth, function(req, res, next) {})...

Also, add a "unauthorized" error handler, to validate our implementation

app.use(function(err, req, res, next) {
   
  if (err.name === 'UnauthorizedError') {
    res.status(401);
    res.json({"message" : err.name + ": " + err.message});
  } else { 
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
  }
});


All done. You should run the application and check the endpoint "/api/catalogue". It has all the details on how to invoke all the apis.
Ideally :
1. Invoke /addUser : to create a new user.
2. Invoke /login : to generate the JWT token.
3. Invoke other APIs using that JWT token.

Happy coding!!

References :

Comments

Post a Comment

Popular posts from this blog

Chatbots and Oracle Cloud Services

Thanks to Oracle A-Team, I had a chance to work with Chatbots.
3 pure NodeJS applications, on couple of Oracle Cloud platforms and Facebook messenger, and my chatbot was running.

Let me explain, the architecture a bit. To start with, following is the simple representation of how it works.

Exception Handling in ADF

This blog will give you an overview on how you can successfully deal with unhandled Runtime exceptions in an ADF application.

This will give you an idea of:
How to catch the unhandled exceptions.Write a separate log file with stacktrace and thread dumps.Redirect the user to an static error page
#1. Catch unhandled exceptions : 

Create a class "MyExceptionHandler" which extends : oracle.adf.view.rich.context.ExceptionHandler. Override handleException() method.

public void handleException(FacesContext facesContext, Throwable throwable, PhaseId phaseId) throws Throwable {
        // this method is going to create a separate file with stacktrace and thread dumps
        writeException(throwable);
        // redirect to error page
        redirectToErrorPage(facesContext);
    }

 Create a folder "services" inside : ViewController\src\META-INF and then create a file named "oracle.adf.view.rich.context.ExceptionHandler".
In the file, add the absolute name of your custom e…