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. Replies
    1. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Front end developer Node js Training in Chennai . learn from or Javascript Online Training from India. Nowadays JavaScript has tons of job opportunities on various vertical industry. JavaScript Training in Chennai

      Delete
  2. Java is highly professional course, and it helps students.Thanks for sharingJavaOnline Training

    ReplyDelete
  3. Really awesome blog. Your blog is really useful for me. Thanks for sharing this informative blog. Keep update your blog...Java Training in Chennai
    Java Training Institute in Chennai

    ReplyDelete
  4. Nice information. I was searching for the same. It helped me alot and saved my time. Thanks a lot.
    node.js development services

    ReplyDelete
  5. 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
  6. Existing without the answers to the difficulties you’ve sorted out through this guide is a critical case, as well as the kind which could have badly affected my entire career if I had not discovered your website.Digital Marketing online training

    full stack developer training in pune

    full stack developer training in annanagar

    full stack developer training in tambaram

    full stack developer training in velachery

    ReplyDelete
  7. Thanks for such a great article here. I was searching for something like this for quite a long time and at last I’ve found it on your blog. It was definitely interesting for me to read  about their market situation nowadays.
    python training in rajajinagar
    Python training in btm
    Python training in marathahalli

    ReplyDelete
  8. This blog is the general information for the feature. You got a good work for these blog.We have a developing our creative content of this mind.Thank you for this blog. This for very interesting and useful.
    Blueprism online training

    Blue Prism Training in Pune

    Blueprism training in tambaram

    ReplyDelete
  9. mytectra placement Portal is a Web based portal brings Potentials Employers and myTectra Candidates on a common platform for placement assistance.

    ReplyDelete
  10. I have been meaning to write something like this on my website and you have given me an idea. Cheers.
    python training in velachery
    python training institute in chennai

    ReplyDelete
  11. Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.

    angularjs Training in chennai
    angularjs-Training in pune

    angularjs-Training in chennai

    angularjs Training in chennai

    angularjs-Training in tambaram

    ReplyDelete
  12. Really very nice blog information for this one and more technical skills are improve,i like that kind of post.
    Devops training in sholinganallur
    Devops training in velachery
    Devops training in annanagar
    Devops training in tambaram

    ReplyDelete
  13. When I initially commented, I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get several emails with the same comment. Is there any way you can remove people from that service? Thanks.

    AWS Interview Questions And Answers

    AWS Training in Pune | Best Amazon Web Services Training in Pune

    AWS Training in Chennai |Best Amazon Web Services Training in Chennai


    AWS Training in Chennai | Best AWS Training in Chennai


    Amazon Web Services Training in Pune | Best AWS Training in Pune

    ReplyDelete
  14. Thank you so much for sharing this. I appreciate your efforts on making this collection. Web Designers in Bangalore | Website Designers in Bangalore | Website Design in Bangalore Bangalore

    ReplyDelete

Post a Comment

Popular posts from this blog

ADF Utility : Find UIComponent from Managed Bean

Many a times, it is required to find an UIComponent from ADF managed bean, to do something with it, for example : change the value of an af:outputText, change readOnly property of an af:inputText etc.

private UIComponent getUIComponent(String id) { 

        FacesContext facesCtx = FacesContext.getCurrentInstance(); 
        return findComponent(facesCtx.getViewRoot(), id);
    } 

    private UIComponent findComponent(UIComponent base, String id) {

        if (id.equals(base.getId())) {
            return base;
        }

        UIComponent children = null;
        UIComponent result = null;
        Iterator childrens = base.getFacetsAndChildren();
        while (childrens.hasNext() && (result == null)) {
            children = (UIComponent)childrens.next();
            if (id.equals(children.getId())) {
                result = children;
                break;
            }
            result = findComponent(children, id);
            if (result != null) {
                break;
            }
  …

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.