PART-1: User Authentication with JWT and Express.js in Node.js: A JOIful Journey
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.
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! 🚀