Building micro-services project on Node series – AuthN & AuthZ

Security in microservices always been a challenge. One has 3 levels of security to take care of while deploying a server. As shown in the below figure. Level 1 is something that all the server needs and we won’t be going to cover in this series. Level 2 is AuthN and AuthZ while level 3 is about inter microservices communication. We will be using JWT for AuthN and AuthZ. 

We will start building a project in a modular pattern. We will have a common project to be imported by all the microservices. An auth microservices will be used to authenticate and provide token respectively.

We will be using Visual Studio Code as a development IDE. 

Development Environment setup

We don’t have to set up much. Following are the files used

  1. .eslintrc.yml

For compilation 

  1. .npmrc

For save default npm behaviour.

Node modules used

    "body-parser": "1.19.0",

    "chalk": "2.4.2",

    "common": "../common", // common dependency

    "cookie-parser": "1.4.4",

    "debug": "4.1.1",

    "express": "4.17.1",

    "morgan": "1.9.1",

    "nodemon": "1.19.3",

    "passport": "0.4.0",

    "path": "0.12.7"

 

Nodemon

Is used to hotswap code and to use environment variables in testing environment.

Debug

It is used to get filtered debug using regular expressions. Even express uses debug. Chalk is used with it to get colour coded console logs.

JWT generation

const ENCODE = require('../auth/jwt.encoder');

const debug = require('debug')('app: generateToken');




const generate = (userdata, done) => {

  const userArray = {

    username: userdata.username,

    name: userdata.f_name,

    roles: userdata.role,

    updatedDate: new Date(),

  };

  // prepare JWT token and send back

  try {

    const token = ENCODE.encodeToken30Days(userArray);

    const build = {};

    build.encoded = token;

    build.plain = userArray;

    return done(null, build);

  } catch (err) {

    done(err);

  }

};




module.exports = {

  generate,

};

 

You have to add things which don’t change much else the client side have to keep on updating/swapping token on a regular basis. Tokens can also be used to maintain server stateless. Here, we are keeping username and user roles in a token.

 

Authentication

const jwt = require('jsonwebtoken');

const debug = require('debug')('app authN')

const AUTH = require('../constants/AUTH');

const STATUS = require('../constants/STATUS');




module.exports = (req, res, next) => {

  debug('In authN');

  var url = req._parsedUrl.pathname;

  debug(`Called URL: ${url}`);

  if (AUTH.API.PUBLIC.includes(url)) {

    debug('Unrestricted public access');

    return next();

  } else {

    const token = req.header('x-auth-token');

    debug(`got token: ${token}`);

    req.plainToken = null;

    if (!token) return res.status(STATUS.UNAUTHORIZED).send('Unauthenticated access!');

    try {

      const decoded = jwt.verify(token, AUTH.SECRET_KEY);

      debug(`debugged token ${JSON.stringify(decoded)}`);

      req.plainToken = decoded;

      next();

    } catch (ex) {

      debug(`Error decrypting token ${JSON.stringify(ex)}`);

      res.status(STATUS.FORBIDDEN).send('Unauthenticated access!');

    }

  }

};

 

We bypass URL which are Public. In authentication we just ensure that, the token received is valid or not. If valid we proceed to the next interceptor.

 

Authorization

const debug = require('debug')('app authZ')

const AUTH = require('../constants/AUTH');

const STATUS = require('../constants/STATUS');




module.exports = (req, res, next) => {

    debug('In authZ');

    const url = req._parsedUrl.pathname;

    if (AUTH.API.PUBLIC.includes(url)) return next();

    let found = false;

    if (req.plainToken) {

        const token = req.plainToken;

        debug(`got roles ${JSON.stringify(token.roles)}`);

        if (token.roles) {

            debug('got roles');

            token.roles.forEach(element => {

                debug(AUTH.API[element]);

                if (AUTH.API[element].includes(url)) found = true;

            });

        }

    }

    debug(`found: ${found}`);

    if (found) next();

    else res.status(STATUS.UNAUTHORIZED).send('Unauthorized request!');

};

 

In AuthZ we filter users based on his roles. It allows only if he has the roles to access that URL.

 

I have put PostMan files for testing in the Git repo.