PART-1: User Authentication with JWT and Express.js in Node.js: A JOIful Journey

Vivekumar08
5 min readNov 11, 2023
Photo by Markus Spiske on Unsplash

In the vast world of web development, ensuring the security of user data is a top priority. One key aspect of this is user authentication, which verifies the identity of users before granting access to certain resources. In this article, we’ll take a journey through the JOIful world of User Authentication using JSON Web Tokens (JWT) and Express.js in Node.js — a combination that brings simplicity and security to the forefront.

Understanding the Basics

Before we dive into the technicalities, let’s grasp the basics. JSON Web Tokens, or JWTs, are a compact, URL-safe means of representing claims between two parties. In simpler terms, JWTs are like digital passports that can be handed between different parts of your application to ensure that a user is who they say they are.

Express.js, on the other hand, is a web application framework for Node.js. It simplifies the process of building robust web applications and APIs. Together with Node.js, Express.js forms a powerful duo for handling server-side logic.

JOI of Validation

JOI is a powerful validation library for JavaScript. It helps ensure that the data coming into our application is of the right shape and format. Think of JOI as a friendly bouncer at the entrance of a party, making sure everyone’s dressed appropriately.

In the context of user authentication, JOI allows us to validate and sanitize user input, ensuring that the data we receive is safe and matches our expectations. This is crucial for preventing security vulnerabilities such as SQL injection or malicious code execution.

Prerequisites

  • Node.js and npm are installed on your system.
  • Basic understanding of JavaScript, Node.js, joi, and MongoDB

Let’s get Started

Step 1: Set Up a New Node.js Project

First things first, let’s set up our Node.js environment. Create a new directory for your project and initialize it with npm. You can do this by running:

mkdir user-authentication
cd user-authentication
npm init -y

Step 2: Install Required Packages

Install Express.js, MongoDB, Mongoose, bcryptjs (password hashing), jsonwebtoken (generating JWTs), dotenv (Securing Secrets Simply), nodemon (Effortless hot-reloading), and Joi (for request validation):

npm i express mongoose bcryptjs jsonwebtoken joi cors dotenv nodemon uuid --save

Step 3: Folder Structure

I used this folder structure, you can use your choice or you can follow this folder structure

user-authentication
│ .env
│ .gitignore
│ package-lock.json
│ package.json
│ server.js

└───api
├───controllers
│ └───auth
│ refreshToken.js
│ user.js

├───dal
│ dal.js

├───middlewares
│ auth.js
│ db.js
│ error-handler.js
│ response-handler.js
│ validator.js

├───models
│ └───auth
│ refreshToken.js
│ user.js

├───routes
│ └───auth
│ refreshToken.js
│ user.js
| index.js

├───service
│ └───auth
│ refreshToken.js
│ user.js

├───utils
│ utils.js

└───validators
└───auth
user.js

Step 4: Set up MongoDB models

Create a models/user.js file to define the users' schema and model:

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const userSchema = Schema({
name: {
type: String,
index: true
},
userName: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String,
index: true
},
countryCode: {
type: String
},
phone: {
type: String,
index: true
},
active: {
type: Boolean,
default: false
},
}, {
timestamps: true
})

module.exports = mongoose.model("users", userSchema);

Create next models/refreshToken.js file to define the refreshToken schema and model:

const mongoose = require('mongoose');
Schema = mongoose.Schema;


const refreshTokenSchema = Schema({
userId: {
type: Schema.ObjectId,
ref: 'users',
unique: true
},
token: {
type: String
}
}, {
timestamps: true
});

refreshTokenSchema.index({ updatedAt: 1 }, { expireAfterSeconds: 30 * 86400 });
module.exports = mongoose.model('refreshtokens', refreshTokenSchema);

Step 5: Set up validation with Joi

Create a validators/auth/user.js file to validate user input using Joi:

const Joi = require("joi");

const addUser = Joi.object({
phone: Joi.string().trim(),
name: Joi.string().trim().required(),
userName: Joi.string().trim(),
email: Joi.string().email().trim(),
password: Joi.string().required()
});

const loginSchema = Joi.object({
userName: Joi.string().trim(),
name: Joi.string().trim(),
email: Joi.string().email(),
phone: Joi.string().trim(),
password: Joi.string(),
mode: Joi.string().trim().valid("phone", "email")
});

const defaults = {
'abortEarly': false, // include all errors
'allowUnknown': true, // ignore unknown props
'stripUnknown': true // remove unknown props
};

const message = (error) => { return `${error.details.map(x => x.message).join(', ')}`; };

module.exports = {
addUser,
loginSchema,
defaults,
message
}

Step 6: Set up routes

Create a routes/auth/user.js file to define the authentication routes:

const router = require('express').Router();
const user = require('../../controllers/auth/user');

const { verifyAccessToken } = require("../../middlewares/auth");

const validate = require('../../middlewares/validator');

const userValidator = require("../../validators/auth/user"); // Validation Schema

router.route('/signup').post(validate(userValidator.addUser), user.signup);
router.route('/login').post(validate(userValidator.loginSchema), user.login);
router.route('/').get(verifyAccessToken,user.getUser);
router.route('/logout').delete(verifyAccessToken, user.logout);

module.exports = router;

Create next routes/auth/refreshToken.js file to define the authentication routes:

const router = require('express').Router();

const refreshToken = require('../../controllers/auth/refreshToken');

const { verifyRefreshToken } = require("../../middlewares/auth");


router.route("/").get(verifyRefreshToken, refreshToken.getAccessToken);
router.route("/:userId").delete(refreshToken.deleteRefreshToken);


module.exports = router;

The final file for all routes routes/index.js

const express = require("express");
const app = express();

const user = require('./auth/user');
const refreshToken = require("./auth/refreshToken");

app.use('/refreshToken', refreshToken);
app.use('/user', user);


module.exports = app;

Step 7: Setup Express server

Write server.js which have some middleware and database MongoDB connection

'use strict';
const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
require("dotenv").config();
const db = require("./api/middlewares/db");

const routes = require('./api/routes/index');

const { useErrorHandler } = require('./api/middlewares/error-handler');

const app = express();

app.use(express.static("public"));

app.use(bodyParser.json({ limit: "50mb", strict: false }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true, parameterLimit:50000 }));
app.use(cors());
app.use(db.connectToDatabase);
app.use('/api/v1/', routes);

const portNumber = process.env.PORT || 5000;
app.listen(portNumber, (err) => {
console.log("portNumber ", process.env.PORT);
if (err) {
console.log(err);
} else {
console.log(`Listening on port ${portNumber}`);
}
});

app.use(useErrorHandler);

So, in simple terms, we are creating a server using Express, setting up some tools for it to use, connecting it to a database, defining how it should handle different requests, and then starting it so that it can respond to requests from the internet. If something goes wrong, there’s a tool in place to handle errors.

Photo by Semyon Borisov on Unsplash

Conclusion

In this adventure, we’ve successfully crafted a robust backend for user authentication using Express.js and MongoDB. By leveraging the power of JWTs, we’ve mastered the art of managing user sessions securely. As we bid adieu to this, remember that learning is a perpetual journey.

Now, brace yourself for the next leg of our exploration! Navigate to part 2 for deeper insights into refining our backend masterpiece. Have a fantastic day ahead on your coding journey! 🚀

--

--

Vivekumar08

A passionate Developer with innovative ideas into reality. With an experience in the MERN stack and cutting-edge Nextjs technology, dynamic approach to web dev.