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 it 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
endpoint with the following JSON:

Client request

{

"deleted": false,
"active": true,
"email": "sibinmuhammed@gmail.com",
"password": "Alien@gmail.com"
}

Server response

{
"data": {
"privilages": [],
"createdTime": "2020-11-07T09:12:01.427Z",
"deleted": false,
"active": true,
"_id": "5fa665b1f9cb144967219c9a",
"email": "sibinmuhammed@gmail.com",
"password": "$2a$10$NniUgcm0MBlUvmiHgotBDuZHkjmfjqOIX92/EIiYUPPx6MjlLHzwi",
"__v": 0,
"id": "5fa665b1f9cb144967219c9a"
}
}


2.Login

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

Client Request

{
"email": "sibinmuhammed@gmail.com",
"password":"Alien@gmail.com"
}

You should get the access token as the response:

Server Response

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1aWQiOiI1ZmE2NjViMWY5Y2IxNDQ5NjcyMTljOWEiLCJwcml2aWxhZ2V
zIjpbXSwiZW1haWwiOiJzaWJpbm11aGFtbWVkQGdtYWlsLmNvbSIsImV4cGl
yeVRpbWUiOjE2MDQ3NjI0MDM3NjgsImlhdCI6MTYwNDc0MDgwM30.OINHFJfs
-mLsV3spDV-4_bcUTIyLScevCEOYEy6lV7A"
}




3.Fetch All User

Let's send a get request to the http://localhost:8000/user/getAll
endpoint with the following JSON:

Authorization
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1aWQiOiI1ZmE2NjViMWY5Y2IxNDQ5NjcyMTljOWEiLCJwcml2aWxhZ2VzI
jpbXSwiZW1haWwiOiJzaWJpbm11aGFtbWVkQGdtYWlsLmNvbSIsImV4cGlyeVRp
bWUiOjE2MDQ3NjI0MDM3NjgsImlhdCI6MTYwNDc0MDgwM30.OINHFJfs-mLsV3spDV
-4_bcUTIyLScevCEOYEy6lV7A

Server Response

{
"data": [
{
"privilages": [],
"createdTime": "2020-11-07T09:12:01.427Z",
"deleted": false,
"active": true,
"_id": "5fa665b1f9cb144967219c9a",
"email": "sibinmuhammed@gmail.com",
"password": "$2a$10$NniUgcm0MBlUvmiHgotBDuZHkjmfjqOIX92/EIiYUPPx6MjlLHzwi",
"__v": 0,
"id": "5fa665b1f9cb144967219c9a"
}
]
}



Run:
Step 2:npm install
Step 3:npm start
Console:
sibinmuhammed@ladmin-H310M-S2:~/Desktop/express_node_jwt$ npm start > node_app@1.0.0 start /home/sibinmuhammed/Desktop/express_node_jwt > nodemon index.ts [nodemon] 2.0.6 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: ts,json [nodemon] starting `ts-node index.ts` App listening on port 8000!










Popular posts from this blog

Spring boot video streaming example-HTML5

DataTable-Pagination example with Spring boot, jQuery and ajax

10 Best Job Posting Sites 2021-2022

Spring boot web project free download:User Registration System

Spring Boot + JPA/Hibernate One to Many mapping example

5 Hardest Puzzle,100% fail answers

Java security AES,SHA256,SHA512,MD5-Spring Boot Project Free Download

Spring Boot-AngularJS-Bootstrap-JPA-CRUD

ReactJS - Bootstrap - Buttons

Spring Boot file upload/download example