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
Unit Testing using *mocha.js and [should.js(Assertion Library)](github.com/shouldjs/should.js)*
API Testing via *POSTman*
REQUIREMENTS
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)*.