5 Steps to Authenticating Node.js with JWT
In this article, I’ll be walking you through 5 steps with which you can integrate JWT authentication into your existing project.
For those following my series, we’ve got a todo list app, and we have written tests for the app. By the end of this tutorial, only registered user will be able to create a todo task, which means the app users are required to register.
Now, let's start with some basics first.
What is JWT?
A JSON Web Token(JWT), defines an explicit, compact, and self-containing secured protocol for transmitting restricted informations. This is often used to send information that can be verified and trusted by means of a digital signature.
The JWT Claims Set represents a compact URL-safe JSON object, that is base64url encoded and digitally signed and/or encrypted. The JSON object consists of zero or more name/value pairs (or members), where the names are strings and the values are arbitrary JSON values. These members are the claims represented by the JWT.
Here's an example of JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im9sYXR1bmRlZ2FydWJhQGdtYWlsLmNvbSIsImZ1bGxOYW1lIjoiT2xhdHVuZGUgR2FydWJhIiwiX2lkIjoiNThmMjYzNDdiMTY1YzUxODM1NDMxYTNkIiwiaWF0IjoxNDkyMjgwMTk4fQ.VcMpybz08cB5PsrMSr25En4_EwCGWZVFgciO4M-3ENE
JSON Web Tokens consist of three parts separated by dots(i.e. header.payload.signature
)
How JWT works
JWT works as a two way protocol where a request is made and the response is generated from a server.
The browser or the requesting device makes the request(user login information for authentication) for JWT encoded data, the server generates the signed token and return to the client(Mobile device/browser) as shown in the diagram above.
Subsequently, the token can be sent over to the http request for every other request that needs authentication on the server. The server then validates the token and, if it’s valid, returns the secure resource to the client.
This token is often signed using any secure signature method (e.g Asymmetric key algorithm such as HMAC SHA-256 or Asymmetric, public-key system, such as RSA).
Advantages JWT?
-
Compact: JWT is compact, which means it can be sent along with http request either as body or as a header attribute.
-
Stateless/self-contained: The token contains all the information to identify the user, which eliminates the need for the session state. If we use a load balancer, we can pass the user to any server, instead of being bound to the same server we logged in on. This also enhance performance, since there is no server-side lookup for deserialization on each request
-
Reusability: We can have many separate servers that run on multiple platforms and domains and reuse the same token for authenticating the user. It is easy to build an application that shares permissions with another application.
-
Security: There is no need to worry about cross-site request forgery (CSRF) attacks.
Prerequisite:
- Node.js
- Node.js app
Tools:
- Jsonwebtoken package,
- Bcrypt package, and
- Postman.
Now, let's authenticate/protect some routes.
Steps:
1. Install “jsonwebtoken” package npm install jsonwebtoken --> save
Jsonwebtoken is a Node package module that developed against draft-ietf-jose-json-web-signature-08
with Certificate Chain signing/verifying.
2. Create the user model
In the api/models folder, create a file called user userModel.js
by running touch api/models/userModel.js
.
As you know, MongoDB enables us to create a schema, where we can create documents. We will use this to create user directly in the User document.
In this file, create a mongoose schema with the following properties:
- fullName
- email address
- password
- the created date
3. Create the user handlers (i.e. sign in, register, and login required)
In the api/controllers folder, create a file called user userController.js
by running touch api/controllers/userController.js
In the userController
file, create three different handlers to handle
- registration ~ register,
exports.register = function(req, res){}
- sign in ~ sign_in
exports.sign_in = function(req, res){}
and
- login required ~ loginRequired
exports.loginRequired = function(req, res){}
In the register handler, we instantiate the user model with the required User schema and then, this is saved in the MongoDB.
exports.register = function(req, res) {
var newUser = new User(req.body);
newUser.hash_password = bcrypt.hashSync(req.body.password, 10);
newUser.save(function(err, user) {
if (err) {
return res.status(400).send({
message: err
});
} else {
user.hash_password = undefined;
return res.json(user);
}
});
};
Note: A hash password was saved in the database using bcrypt.
The sign_in
handler handles the user sign in activities. Hence, we need to check and see if the user is already saved in the database. If yes, we have to check if the entered parameters matches the one saved by the user in the database.
If the user or the entered parameters do not match with the saved data, we return an error with a defined message.
exports.sign_in = function(req, res) {
User.findOne({
email: req.body.email
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(401).json({ message: 'Authentication failed. User not found.' });
} else if (user) {
if (!user.comparePassword(req.body.password)) {
res.status(401).json({ message: 'Authentication failed. Wrong password.' });
} else {
return res.json({token: jwt.sign({ email: user.email, fullName: user.fullName, _id: user._id}, 'RESTFULAPIs')});
}
}
});
};
In loginRequired
, we check to see if the user is signed in.
If the user is signed in, the user is allowed to carry out the next activities; else, we return a user unauthorized message in that instance, like so:
exports.loginRequired = function(req, res, next) {
if (req.user) {
next();
} else {
return res.status(401).json({ message: 'Unauthorized user!' });
}
};
The diagram below shows the detailed created user controller.
As shown in the diagram above, our model and JWT were made available to the files that require them first and foremost.
In the register handler, we instantiated our User model with the form data, after which we saved the document in the database.
Note: Always perform form validation on the body properties before saving them to User model.
4. Create the user routes and update “/tasks” route,
Since we have some routes before, let’s update it by adding two other routes for the user activities.
First and foremost, let’s make the user handlers available in the routes, before we create the routes by updating:
var todoList = require(‘../controllers/todoListController’);
to
var todoList = require(‘../controllers/todoListController’),
userHandlers = require(‘../controllers/userController.js’);
Here is a route for the user to register/signup on the app, and a route for the user to be able to sign in/have access to the app with their created credentials:
After creating the route, add the loginRequired
handler to the post request on "/tasks
".
This will ensure that the user is logged in before he/she can create a todo task.
server.js
file
5. Updating the Going over to the server.js
file, we need to add the newly created User model and also add a middleware to the express server that will check the state of the user(logged in or not logged in)
To add the user, add
var User = require('./api/models/userModel'),
jsonwebtoken = require("jsonwebtoken");
to the required file at the top of the file.
Just before the point where the app routes are instantiated, we will add the express middleware to the server.js
file.
This will ensure that the middleware runs before the routes.
Now, let's head over to postman to test our registration route after running:
npm run start
on the terminal.
If you have followed everything correctly, we should have:
Let’s test what we have done so far via a Postman:
Launch postman --> select POST method --> add localhost:3000/tasks
(the port the app is listening on) to the form field keys and values in the URL section --> click send.
A status code of 401 shows that our authentication is working with a response
{
“message”: “Unauthorized user!”
}
Now, let’s create a user, then sign the user in for us to have access to create a todo task.
On the postman, change the URL to
/auth/register
,enter the key and values for fullName, email, and password, and click send. This should give you a response like so:
After this, let’s sign the user in with the credentials we just created by changing the URL to localhost:3000/auth/sign_in
. Enter the keys and values for email and password as the case maybe.
On send, a status of 200 and the generated token is returned.
Add this token to the authorization header by clicking on the header.
Under the value, add JWT and the token with a space between, like so:
JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im9sYXR1bmRlZ2FydWJhQGdtYWlsLmNvbSIsImZ1bGxOYW1lIjoiT2xhdHVuZGUgR2FydWJhIiwiX2lkIjoiNThmMjYzNDdiMTY1YzUxODM1NDMxYTNkIiwiaWF0IjoxNDkyMjgwMTk4fQ.VcMpybz08cB5PsrMSr25En4_EwCGWZVFgciO4M-3ENE
Then, enter the parameters for the key and value for the todo task. You want to create as shown below and send:
Wrapping it up
With these 5 steps, we have been able to add authentication to Node.js App. The complete code can be found at Github authentication branch.
Here are some other articles in the series:
My other posts:
Just what i was looking for… Thanks a lot…!
Hi, I am putting up my api in a muti-node environment. So, I have an api running on a port say 8000, which is for our transactions Database and which covers authentication as well. At the same time we have another API running on port 9000, which covers our Reporting APIs. We want to maintain the same session across both the APIs. What are the best practices for achieving this? One option could be put the verify token in the Reporting API as well. I don’t want to repeat the code. Any ideas?
Olatunde,
user.hash_password = undefined;
What is the purpose of this line and why you use it?
I’m able to hash the password but not able to save it on the database.
to return the response without hash_password. in the save function, it will save it into database and return the object.