Authentication is a big part of any application. The way authentication is handled traditionally is by creating a user session on the server where we usually store the user’s information in memory or on disk. This is also known as server/session-based authentication.
Web applications have come a long way in the past few years, we have the stateless web, hand-held devices came along, we have load balancer then came along micro-services architecture. Storing session data on the server made our app less scalable and then sharing the session between multiple services or servers is also a problem. That is why Token Based Authentication 🙂
In this tutorial, we will learn to implement token-based authentication in our node.js applications.
Table of Contents
ToggleWhat is token-based authentication?
Token-based authentication is stateless and session less, meaning when we authenticate the user we do not store any user information on the server. Instead, we generate a token signed by a private key and send it to the client. The way it works is as follows.
- The user makes a request to the server with a username/password
- The server verifies the user.
- The server generates a signed token and provides it to the client.
- The token may contain user data.
- The client stores the token and sends it along with every request.
- The server verifies the token and processes the request.
Tokens can be sent to the server in any way but the best practice tells us to send it in an HTTP header.
Our Application
In this tutorial, we will see how we can easily add token-based authentication using JSON web Tokens in Node.js.
We will build a few APIs using NodeJS and ExpressJS and see how we can protect/authenticate them using JWT’s
We will be using.
- MongoDB as our DB
- ExpressJS for routes
- jsonwebtoken an npm module for managing tokens.
Getting started
Make sure you have node and npm installed. To begin with create a project folder and navigate into it and run npm init --yes. This will create a package.json for us.
Next, we will install the necessary tools for our application.
npm i body-parser express jsonwebtoken mongoose --save
Our, package.json should look like this.
package.json{
"name": "jwt-auth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Rahil Shaikh",
"license": "MIT",
"dependencies": {
"body-parser": "^1.15.2",
"express": "^4.14.0",
"jsonwebtoken": "^7.2.1",
"mongoose": "^4.7.6"
}
}
Folder Structure
We will be following the below folder structure
- config
--- config.js
- controllers
--- index.js
--- protected.js
--- user.js
- middlewares
--- verifyToken.js
- models
--- user.js
- index.js
- package.json
Creating our user model
We are using MongoDB for persistence and mongoose as our ORM. Make sure your machine has MongoDB installed and running.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({
email: String,
password: String
}));
Node Application
We will start in index.js by grabbing the required modules and setting up an express server.
let express = require('express');
let app = express();
let bodyParser = require('body-parser');
let mongoose = require('mongoose');
global.config = require('./config/config');
let jwt = require('jsonwebtoken');
let User = require('./models/user');
mongoose.connect("mongodb://localhost/demo");
app.use(bodyParser.json());
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000, function(){
console.log('App running on 3000');
});
Now, we can start the server by running node index.js. We should be seeing hello world printed on the screen on http://localhost:3000.
Adding unprotected routes
In this section, we will expose an API to signup users and also to authenticate them. In the authenticate API we will create a token for a successful login and send it to the client.
var express = require('express');
var router = express.Router();
router.use('/user',require('./user'));
module.exports = router;
let express = require('express');
let router = express.Router();
let jwt = require('jsonwebtoken');
let User = require('../models/user');
router.post('/signup', function(req, res){
let user = new User({
email: req.body.email,
password: req.body.password
});
user.save(function(err, data){
if(err){
return res.json({error: true});
}
res.json({error:false});
})
});
router.post('/authenticate', function(req, res){
let data = {
email: req.body.email,
password: req.body.password
};
User.findOne(data).lean().exec(function(err, user){
if(err){
return res.json({error: true});
}
if(!user){
return res.status(404).json({'message':'User not found!'});
}
console.log(user);
let token = jwt.sign(user, global.config.jwt_secret, {
expiresIn: 1440 // expires in 1 hour
});
res.json({error:false, token: token});
})
});
module.exports = router;
Note. Here, for simplicity we are just encoding the entire user object. Idealy you should not include sensitive data such as password in your encoded token.
We will also have to modify our main index.js to add routes exposed by our controllers to the app.
...
....
.....
app.use(require('./controllers'));
app.listen(3000, function(){
console.log('App running on 3000');
});
Run the app. We will use postman to test our APIs. Let’s signup first.
Here, we have signed up a user with email as rahil[at]ciphertrick.com and password as the password. Now, let’s try to authenticate with the same credentials.
If you see above, on successful authentication the API sends us a token. This is the token we will need to further validate the user. We can accept the token in the header, in the body, or as a URL param. Also, remember the token has the data which we have signed using our secret key.
Note. In a real application passwords must not be stored as a plain text instead should be hased with a proper alogorithm depending upon your requirements.
Creating a middleware to verify Token
Let’s add an express middleware that will verify the token for us.
var jwt = require('jsonwebtoken');
module.exports = function(req,res,next) {
var token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
// verifies secret and checks exp
jwt.verify(token, global.config.jwt_secret, function(err, decoded) {
if (err) { //failed verification.
return res.json({"error": true});
}
req.decoded = decoded;
next(); //no error, proceed
});
} else {
// forbidden without token
return res.status(403).send({
"error": true
});
}
}
Protecting routes
Now that we have our middleware ready to use, let’s see how we can protect our routes. For this, we will create an API as /protected, which will simply verify the token and send the decoded result.
let express = require('express');
let router = express.Router();
router.get('/', function(req, res){
res.json(req.decoded);
});
module.exports = router;
Modify your index.js file within controllers to look like this.
var express = require('express');
var router = express.Router();
let verifyToken = require('../middlewares/verifyToken');
router.use('/user',require('./user'));
router.use('/protected', verifyToken, require('./protected'));
module.exports = router;
Note that we are using the verifyToken middleware to protect our APIs. Once again restart your server and test the API using postman. Remember if we hit the API without the token it will result in a 403 forbidden error.
Conclusion
We have seen how we can add token-based authentication to our node.js application using jsonwebtoken. This was just a simple use-case to help get an understanding on how token based authentication works. Token based authentication scales well and makes it easier to manage cross devices authentication.