Shams Nahid
Shams Nahid's Blog

Shams Nahid's Blog

Remote Authentication Using JSON WEB TOKEN

Remote Authentication Using JSON WEB TOKEN

Shams Nahid's photo
Shams Nahid

Published on May 13, 2017

8 min read

Check the source-code here.

Authentication is an important portion for web application. From client side, like a mobile application must require a secure authentication to the server and *passport is a prominent helper. Let’s find another [JSON Web Token](npmjs.com/package/jsonwebtoken)* package, that is totally session based authentication.

Here I’m developing simple Node API that is going to create dummy users and also using tokens to authenticate the users from remote client.

Work-Flow

  • Two user routes will be available

  • One is not protected, going to create a user

  • Another is protected, send all the current users on request

  • Also one authentication request, response will be in JSON format with a new token

TESTING

  1. Unit Testing using *mocha.js and [should.js(Assertion Library)](github.com/shouldjs/should.js)*

  2. API Testing via *POSTman*

REQUIREMENTS

  1. *Node.js(7.1.0)*

  2. *NPM(3.10.9)*

  3. *Express.js(4.15.2)*

PROJECT STRUCTURE

First create the following project architecture…

-app/
----controllers/
--------authenticationController/
------------authenticate/
----------------index.js
--------userController/
------------user/
----------------index.js
------------test/
----------------runner.js
----models/
--------userModel/
------------index.js
----routes/
--------index.js
-config/
--------index.js
-Test/
----utils.js
-package.json
-server.js

APP SETUP

This is a production ready application, we separated the developers dependencies and the main dependencies. Here’s the package.json file…

{
  **"name"**: **"jwt-auth"**,
  **"main"**: **"server.js"**,
  **"scripts"**: {
    **"start"**: **"node server.js"**,
    **"test"**: **"mocha --recursive **/test/runner.js"
  **},
  **"dependencies"**: {
    **"body-parser"**: **"^1.17.1"**,
    **"express"**: **"^4.15.2"**,
    **"express-session"**: **"^1.15.2"**,
    **"jsonwebtoken"**: **"^7.4.0"**,
    **"mongoose"**: **"^4.9.8"**,
    **"morgan"**: **"^1.8.1"
  **},
  **"devDependencies"**: {
    **"babel-eslint"**: **"^7.2.3"**,
    **"cors"**: **"^2.8.3"**,
    **"mocha"**: **"^3.3.0"**,
    **"mocha-logger"**: **"^1.0.5"**,
    **"nodemon"**: **"^1.11.0"**,
    **"should"**: **"^11.2.1"
  **}
}

PACKAGE DESCRIPTION

  • We named our main server as server.js

  • Since we use *nodemon* for run the app, add the scripts to automate restart the server while a file is changed.

  • For automate testing purpose all the runner.js file will be fired recursively if that is exist under the test directory.

  • *body-parser* will grab the parameter on Post request.

  • *express* is the server side framework.

  • *express-session* is the session handling middle-ware.

  • *jsonwebtoken* for generate and verify the token

  • *mongoose* is the driver to interact with the mongoDB database.

  • *morgan* is a logger middle-ware for express framework.

  • *babel-eslint* for ECMA-Script 6 watcher.

  • *cors* for filter the clients.

  • *mocha* for Unit Testing in Node.js app.

  • *mocha-logger* for logging the unit test status.

  • *should* for assertion library

INTERMISSION

Go to the project folder and run

npm install

This will install all the package files in your project folder.

Now setup the server.js file …

**var **express = require(**'express'**);
**var **app = express();
**var **bodyParser = require(**'body-parser'**);
**var **logger = require(**'morgan'**);
**var **mongoose = require(**'mongoose'**);

**var **session = require(**'express-session'**);

**var config **= require(**'./config'**);

**var **router = require(**'./app/routes'**);

**var **port = process.**env**.PORT || 3000;
mongoose.connect(**config**.database.**db**, (err) => {
    **if**(err) {
        **console**.log(**'Error in connecting database : ' **+ **config**.database.**db **+ **" Error: " **+ err);
        res.send(500, {**err**: err});
    }
});

app.set(**'superSecret'**, **config**.database.**mySecret**);

app.use(bodyParser.**urlencoded**({**extended**: **false**}));
app.use(bodyParser.**json**());


app.use(logger(**'dev'**));

app.use(**'/api'**, router);

app.listen(port);
**console**.log(**'Server running on port: ' **+ port);

Nothing special. Also setup the Database Configuration config/index.js

**var **db = {
  **db**: **'localhost:27017/jwtTokenAuthentication'**,
  **dbTest**: **'localhost:27017/jwtTokenAuthenticationTest'**,
  mySecret: **'Eminem'
**};

module.exports = {
    **database**: db
};

One database for real data, another for testing purpose, I don’t want to messed up with the actual data while testing the application.

ROUTING

In your app/routes/index.js file …

**var **base = process.**env**.PWD;
**var **express = require(**'express'**);
**var **router = express.Router();
**var **userController = require(base + **'/app/controllers/userController/user'**);
**var **authenticateController = require(base + **'/app/controllers/authenticationController/authenticate'**);


router.get(**'/users'**, authenticateController.checkAuthentication, userController.getUsers);
router.**post**(**'/user'**, userController.createUser);
router.**post**(**'/authenticate'**, authenticateController.authenticateUser);

module.exports = router;

Here exists 3 routes,

  • One for get all the users, that will invoke getUsers method from the userController Object, also verify the token

  • Another for create the users, that will invoke createUser Method form userController object.

  • The third one is to authenticate the user by creating a json token.

    Get Users is protected by the token here

USER OPERATION CONTROLLERS

Two user controllers method placed here, one for create user and another for get the list of the users.

Put these code in app/controllers/userController/user/index.js

**var **base = process.**env**.PWD;
**var **User = require(base + **'/app/models/userModel'**);

var createUser = (req, res) => {
    var user = new User(req.body);
    user.save((err, user) => {
        if(err) { res.send(500, {err: err}) }
        else { res.send(200, user); }
    });
};

var getUsers = (req, res) => {
    User.find({}, (err, users) => {
        if(err) { res.send(500, {err: err}) }
        else { res.send(200, users); }
    });
};

module.exports = {
    createUser,
    getUsers
};

AUTHENTICATION CONTROLLER

The authentication controller has the job to generate and verify the token. These code go into app/controllers/AuthenticateController/authenticate/index.js

**var **base = process.**env**.PWD;
**var **jwt = require(**'jsonwebtoken'**);
**var **User = require(base + **'/app/models/userModel'**);
**var **config = require(base + **'/config'**);

var authenticateUser = (req, res) => {
    User.findOne({name: req.body.name}, (err, user) => {
        if(err) { throw err; }
        if(!user) {
            res.send(500, {
                success: false,
                message: 'Authentication failed: User not Found'
            });
            res.json(500, {
                success: false,
                message: 'Authentication failed: User not Found'
            });
        } else if(user.password != req.body.password) {
            res.send(500, {
                success: false,
                message: 'Authentication failed: Password dose not match'
            });
        } else {
            var token = jwt.sign(user, config.database.mySecret, {
                expiresIn: 1440  //24 hours
            });
            res.send(200, {
                success: true,
                message: 'Token Created !!!',
                token: token
            });
        }
    });
};

var checkAuthentication = (req, res, next) => {
    var token = req.body.token || req.query.token || req.headers['x-access-token'];
    if(token) {
        jwt.verify(token, config.database.mySecret, (err, decoded) => {
            if(err) {
                return res.json({
                    success: false,
                    message: 'Failed to authenticate token'
                });
            } else {
                req.decoded = decoded;
                next();
            }
        });
    } else {
        return res.status(403).send({
            success: false,
            message: 'No Token Provided'
        });
    }
};

module.exports = {
    authenticateUser,
    checkAuthentication
};

DATABASE SCHEMA

Though database schema is not required mongoDB, for simplicity a simple schema will be defined. Only name, password and administrative status will be used here. To keep things simple password encryption is skipped. You must use some tools to encode and decode the password in production.

So database schema goes to app/models/userModel/index.js

**var **mongoose = require(**'mongoose'**);
**var **Schema = mongoose.Schema;
mongoose.**Promise **= global.*Promise*;

var userSchema = new Schema({
    name: { type: String, Required: true},
    password: { type: String, Required: true},
    admin: { type: Boolean, Required: false, default: false}
});

module.exports = mongoose.model('User', userSchema);

For details about the database schema, I recommend the *documentation*.

HELLO WORLD

If everything is Ok, then run

nodemon

or

node server.js

In if you are done accurately, the console should display

Server running on port 3000

Now your app is running. I would recommend to use nodemon to run the server.

TESTING

For test purpose: authentication, getUsers and createUser will be tested in postMan. It’s the moment of truth :(

Create User

For creating an user we must provide the name and password.

  • Address should be http://localhost:3000/api/user

  • Set headers ‘content-type’ : ‘application/x-www-form-urlencoded’

  • In body mark x-www-form-urlencoded

  • Set value testName for name and testPassword for password.

In return you must get the created user information.

Authenticate User

To get authentication, the user have to provide the correct name and password. On success, the authenticate user would receive a token.

  • Address should be http://localhost:3000/api/authenticate

  • Set headers ‘content-type’ : ‘application/x-www-form-urlencoded’

  • In body mark x-www-form-urlencoded.

  • Set value testName for name and testPassword for password.

Get Users

Time to use our retrieved token. The retrieved token can be passed through the address parameter or headers or query. If the token is verified then we will get all the created users information.

  • Address should be http://localhost:3000/api/users

  • Set headers ‘content-type’ : ‘application/x-www-form-urlencoded’, ‘x-access-token’: ‘retrieved token’

Now all the users should be appeared in json format. In this case only one user exist. But try to create several users and check the list.

I won’t go deep on Unit Testing the app. Check my source to ensure testing. Run the following command to test the app. Add a test helper in Test/utils.js

var responseValidatorAsync = (expectedStatusCode, validationFunction) => {
    return {
        json: (statusCode, data) => {
            statusCode.should.equal(expectedStatusCode);
            validationFunction(data);
        },
        send: (statusCode, data) => {
            statusCode.should.equal(expectedStatusCode);
            validationFunction(data);
        }
    }
};

module.exports = {
  responseValidatorAsync
};

Also Unit test in the user Controller in app/controllers/userController/test/runner.js

process.**env**.**NODE_ENV **= **'test'**;

**var **base = process.**env**.PWD;
**var **config = require(base + **'/config'**);
**var **logger = require(**'mocha-logger'**);
**var **mongoose  = require(**'mongoose'**);
**var **User = require(base + **'/app/models/userModel'**);
**var **userController = require(base + **'/app/controllers/userController/user'**);
**var **should = require(**'should'**);
**var **testUtils = require(base + **'/Test/utils'**);

describe(**'API TESTING'**, () => {
    **var **id, dummyUser, userToken;

    **before**((done) => {
        mongoose.connect(config.database.**dbTest**, (err, user) => {
            **if**(err) { logger.*log*(**'Error in connecting database: ' **+ err) }
            **else **{ done(); }
        });

        dummyUser = **new **User({
            **'name'**: **'dummyName'**,
            **'password'**: **'dummyPassword'
        **});

        dummyUser.save((err, user) => {
            **if**(err) { logger.*log*(**'Error in saving user: ' **+ err); }
            **else **{ id = user._id; }
        });

    });

    describe(**'CREATE USER'**, () => {
        it(**'should create a new user'**, (done) => {
            **var **req = {
                **body**: {
                    **'name'**:**'new user'**,
                    **'password'**: **'new password'
                **}
            };
            **var **res = testUtils.*responseValidatorAsync*(200, (user) => {
                user.should.have.*property*(**'name'**);
                user.**name**.should.equal(**'new user'**);
                done();
            });
            userController.*createUser*(req, res);
        });
    });

    describe(**'GET ALL USERS'**, () => {
        it(**'should get all the users'**, (done) => {
            **var **req = {};
            **var **res = testUtils.*responseValidatorAsync*(200, (users) => {
                users.length.should.have.equal(2);
                users[0].should.have.*property*(**'name'**);
                users[0].name.should.equal(**'dummyName'**);
                done();
            });
            userController.*getUsers*(req, res);
        });
    });

    **after**((done) => {
        User.remove({}, (err) => {
            **if**(err) { logger.*log*(**'Error in removing all users from database: ' **+ err); }
            mongoose.disconnect(done);
        });
    });
});

To run the Unit Test

npm test

Voila, test result will be displayed in the terminal.

CONCLUSION

Hope you got an idea about the remote authentication using JSON WEB Token. Also check the source code *here. If there is anything about concept or the code response below or create an issue on [github](github.com/bmshamsnahid/Medium-Blog-JWT)*.

 
Share this