Node JS: Authentication and Authorization with JWT in Express.js

Hello everyone, Today we will learn how to use the JWT token to handle authentication and authorization in Express. You can download the source code from our GitHub repository, Download link is provided at the end of this post.

What is JSON Web Token?

JSON Web Tokens (JWT) have been introduced as a self-contained way for securely transmitting information between parties as a JSON object.JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

When should you use JSON Web Tokens?

  • Authorization: Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
  • Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs.
Here is the diagram of how JWT works:


Advantage of Using JWT

JWT can contain all of the information about the user itself, unlike session-based authentication.

This is very useful for scaling web app with micro-services. Today, the architecture of a modern web app looks like something similar to this:


If we use traditional authorization methods, we will have to share a cache database, like Redis, to share the complex information between servers or internal services. But if we share the secret across the micro-services, we can just use JWT, and then no other external resources are needed to authorize users.

Using JWT with Express

Today we will develop a simple microservice-based web app to manage users with three services.
  1. Add user (No authentication/authorization required)
  2. Login
  3. Get all users (Authentication/authorization required)

Project Structure:



package.json

A package.json is a JSON file that exists at the root of a Javascript/Node project. It holds metadata relevant to the project and is used for managing the project's dependencies, scripts, version and a whole lot more.
{
"name": "node_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.ts",
"seed": "ts-node ./seed/seed.ts",
"lint": "eslint . --ext .ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose": "^5.7.20",
"@types/node": "^14.0.1",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongodb": "^3.5.7",
"mongoose": "^5.9.14",
"mongoose-seed": "^0.6.0",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"nodemon": "^2.0.3"
}
}


index.ts

index.ts typically handles your app startup, routing, and other functions of your application and does require other modules to add functionality.

import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import userRoute from './routes/user.routes';

const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
app.use('/user', userRoute);
app.listen(8000, () => {
// eslint-disable-next-line no-console
console.log('App listening on port 8000!');
});


user.model.ts 

Define a schema and a model

import mongoose from 'mongoose';
import collections from '../collections';
import { User } from '../interface/user.interface';

const schema = new mongoose.Schema(
{
email: {
type: String,
unique: true,
required: true,
index: true,
},
password: {
type: String,
required: true,
},
privilages: [
{ type: Number },
],
createdTime: {
type: Date,
default: Date.now(),
},
deleted: {
type: Boolean,
default: false,
},
active: {
type: Boolean,
default: true,
},
},
);

schema.set('toJSON', { virtuals: true });

export = mongoose.model<User>(collections.users, schema);


user.interface.ts

Interface patterns are still useful and it’s fairly easy to create something in JavaScript that contains the proper abstractions to benefit the code that helps a project.

import { Document } from 'mongoose';

export interface User extends Document{
id: string;
email: string;
password: string;
privilages: Array<number>;
deleted: boolean;
active: boolean;
}


jwt.ts

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import {
secret, code, msg, expiryTime,
} from './constants';
import { models } from '../db/db';
import { User } from '../db/interface/user.interface';

const { UserDoc } = models;

export type AuthUser = {
uid: string;
email: string;
privilages: Array<string>;
expiryTime: number;
}

export interface UserRequest extends Request {
user: AuthUser;
}

export function createToken(user: User): string {
const token = jwt.sign({

uid: user.id,
privilages: user.privilages,
email: user.email,
expiryTime: Date.now() + expiryTime,
}, secret);

return token;
}

export function authenticate(
req: UserRequest,
res: Response,
next: NextFunction,
): Response | void {
const authHeader = req.headers.authorization;

if (authHeader) {
const token = authHeader.split(' ')[1];

return jwt.verify(token, secret, async (err: Error, user: AuthUser) => {
if (err) {
return res.status(code.FORBIDDEN).send({ message: msg.forbidden });
}

const userDetails = await UserDoc.findById(user.uid);

if (userDetails && userDetails.active
&& !userDetails.deleted && Date.now() <= user.expiryTime) {
req.user = user;
return next();
}

return res.status(code.FORBIDDEN).send({ message: msg.forbidden });
});
}
return res.status(code.FORBIDDEN).send({ message: msg.forbidden });
}


user.service.ts

import bcrypt from 'bcryptjs';
import { Request, Response } from 'express';
import { code, msg } from '../helpers/constants';
import { isEmpty } from '../helpers/validation';
import { models } from '../db/db';
import { User } from '../db/interface/user.interface';
import { createToken } from '../helpers/jwt';

const { UserDoc } = models;

async function authenticateUser(email: string, password: string): Promise<User> {
const user: User = await UserDoc.findOne({ email, deleted: false });

if (user) {
if (bcrypt.compareSync(password, user.password)) {
return user;
}
}
return null;
}
export async function add(request: Request, response: Response): 
Promise<Response> {
try {
console.log(request.body)
let hash = bcrypt.hashSync(request.body.password, 10);
request.body.password = hash;
const data = new UserDoc(request.body);
await data.save();
return response.status(200).send({ data: data });
} catch (error) {
console.log(error)
return response.status(code.SERVER_ERROR).
send({ message: msg.unknownError });
}
}
export async function login(request: Request, response: Response): 
Promise<Response> {
try {
if (isEmpty(request.body.email)) {
return response.status(code.BAD_REQUEST).
send({ message: msg.emailRequired });
}

if (isEmpty(request.body.password)) {
return response.status(code.BAD_REQUEST).
send({ message: msg.passwordRequired });
}

const user = await authenticateUser(request.body.email, 
request.body.password);

if (user) {
if (!user.active) {
return response.status(code.UNAUTHORIZED).
send({ message: msg.inActiveUser });
}

const token: string = createToken(user);
return response.status(code.SUCCESS).send({ token });
}
return response.status(code.UNAUTHORIZED).
send({ message: msg.incorrectCredentials });
} catch (error) {
return response.status(code.SERVER_ERROR).
send({ message: msg.unknownError });
}
}
export async function get(request: Request, response: Response): 
Promise<Response> {
try {
console.log(request.body)

const data = await UserDoc.find();

return response.status(200).send({ data: data });
} catch (error) {
console.log(error)
return response.status(code.SERVER_ERROR).
send({ message: msg.unknownError });
}
}


user.routes.ts

import { Router, Request, Response } from 'express';
import { login, add, get } from '../services/user.service';
import { authenticate } from '../helpers/jwt';
const router = Router();

router.post('/login', (req: Request, res: Response) => {
login(req, res);
});
router.post('/adduser', (req: Request, res: Response) => {
add(req, res);
});
router.get('/getall',authenticate, (req: Request, res: Response) => {
get(req, res);
});
export = router

Test authentication and authorization 

1. Add user (No authentication/authorization required)

Let's send a post request to the http://localhost:8000/user/adduser


2. Login

Let's send a post request to the http://localhost:8000/user/login



3. Fetch All User

Let's send a get request to the http://localhost:8000/user/getall



Run the application on your local machine:


Step 2: npm install

Step 3: npm start

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete