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

  1. Hey, here's a nice article to do this think without using passport.js https://medium.com/@spyna/how-really-protect-your-rest-api-after-social-login-with-node-js-3617c336ebed

    ReplyDelete
  2. how to do this authentication with angular as front end?

    ReplyDelete
  3. I am a regular reader of your blog and being students it is great to read that your responsibilities have not prevented you from continuing your study and other activities. Love
    devops online training

    aws online training

    data science with python online training

    data science online training

    rpa online training

    ReplyDelete
  4. I truly like how your class timings of your online journal. I delighted in perusing your online journal and it is both instructional and intriguing.

    Best Mobile App Development Company

    Web App Development Company

    Blockchain Development

    ReplyDelete
  5. This is very great thinks. It was very comprehensive post and powerful concept. Thanks for your sharing with us. Keep it up..
    Oracle Training in Chennai | Oracle Training Institutes in Chennai

    ReplyDelete
  6. Hey, would you mind if I share your blog with my twitter group? There’s a lot of folks that I think would enjoy your content. Please let me know. Thank you.
    Java Training in Chennai | J2EE Training in Chennai | Advanced Java Training in Chennai | Core Java Training in Chennai | Java Training institute in Chennai

    ReplyDelete
  7. Many thanks to you for offering the learned blog to us. I trust that you will post a lot more sites with us. We give passport login best administrations in the entire world as a diplomatic visa available to be purchased.

    ReplyDelete
  8. Great learning resource with segregated source codes.

    Jeevitha from Way2Smile - Most trusted Mobile App Development Company in Chennai.

    ReplyDelete
  9. Your management skill is your spokesperson for the company cursos de ti online

    ReplyDelete
  10. The blog was absolutely fantastic! Lot of great information which can be helpful in some or the other way. putlocker

    ReplyDelete
  11. Thanks for sharing useful information.. we have learned so much information from your blog..... keep sharing
    DevOps Training in Chennai

    DevOps Course in Chennai


    ReplyDelete
  12. we have provide the best ppc service.
    Piano Tiles 2 Mod

    ReplyDelete
  13. A very well detailed post on passports
    . I like your effort. Please keep posting these types of post

    ReplyDelete

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. Message Platform Server : Is a NodeJS application, deployed on Oracle Application Container cloud, acts as a channel between Facebook Messenger and the chatbot engine. It simply converts the incoming messages from Facebook and sends it to chatbot readable format. Also, when chatbot replies, it converts to Facebook readable formats and passes it to messenger. Chatbot Engine : Is a NodeJS application, which communicate with some REST APIs based on a conversation flow document and moves the flow of the conversation from one state to another. Flow JSON : Where we document, every state of a conversation and which APIs to call to generate a response. For example, at the beginning of the con

Create Micro CRUD services for Oracle Database Cloud using NodeJS

I will try to explain, how you can use NodeJS to create mirco services for the tables in your Oracle Database Cloud or on-premise Database. Complete Github project : https://github.com/sohamda/LeasifyAPIs You need to do "npm install" to download the node_modules. Step by Step guide : 1. NodeJS : either 32 or 64 bit. If you already have NodeJS installed, please check whether it is 64 or 32. Use below command to figure that out : C:\>node > require('os').arch() If you get : 'ia32' , then it is 32 bit installation. 2. Install oracle-db node module .  This was a lengthy and time consuming installation for me, because for Windows, it has a lot of pre-requisites. If you are a Mac user, you are lucky. :) I followed : https://community.oracle.com/docs/DOC-931127 There is also a detailed one in github : https://github.com/oracle/node-oracledb/blob/master/INSTALL.md 3. Config your DB Cloud Create a user and couple of tables on which we'

Layout Management & CSS Classes with Oracle JET

Oracle JET provides automatic responsive layout using CSS classes. So that, from large screens to small screens the application fits itself the best possible way. JET’s layout management are based on 2 types of CSS classes “Responsive Grid” and “Flex”. Responsive grid classes which deals with size, number of columns and functions of a particular <div>. Naming convention of these classes are oj- size - function - columns sizes can be: sm, md, lg, xl functions can be: hide, only-hide columns can be: any number between 1 to 12.   Just like Bootstrap, JET also divides the width of the available space into 12 columns, so for example, if you want a section of your page should take up atleast 5 columns if you divide the available screen into 12 columns, you need use : oj- size -5. Now comes the size part, you need to define that for each size of the screen, from hand-held mobile devices to large or extra large desktop screens. With combination with theses grid c