Authenticate Rest APIs in Node JS TypeScript using JWT (JSON Wen Token)

nitish96
Nitish Sharma
Published on: October 30, 2024
Updated on: November 1, 2024
Authenticate Rest APIs in Node JS TypeScript using JWT (JSON Wen Token) blog

Step 1- Setup Express JS project

  1. Create a new directory and install the node modules
        $ makdir authentication
$ cd authentication
$ npm init -y

     2. After that let's add Typescript to the project. we can do that by running the following command.

      $ npm i -g typescript
$ npm i -D typescript

    3. Add express to the project. we can do that by running the following command.

     $ npm i express
$ npm i -D @types/express

   4. Open project in visual studio code.Create new folder in project root directory, which name is src.Under src create file index.ts.

     // index.ts
import express from 'express';

const app = express();
const PORT = 3000;
app.listen(PORT, ()=>{
console.log(`Running on Port ${PORT});
})

   5. Now open terminal and run node project. 

 $ node ./src/index.ts

When you are run this command, this will giving error

Solve this problem create tsconfig.json. We can do by running following command.

$ npx tsc --init

5. Add some change in tsconfig.json

"rootDir":"./src",
"outDir":"./dist",
"noImplicitAny": true,

tsconfig.json

6. Generate dist folder in root directory. You can generate folder using following command.

  $ npx tsc --build

7. Now try to run project using command

$ node ./dist/index.js

Congratulations, i are setup node js project successfully. 

8. Add some change in package.json, which help us to real time run project without every time build  project then run project for changes reflect.

// package.json 
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node dist/index.js",
    "build": "rimraf dist && tsc",
    "start:dev": "nodemon ./src/index.ts"

  },

9. Install nodemon,

nodemon is a tool that helps develop Node.js based applications by automatically restarting the node application when file changes in the directory are detected.

nodemon does not require any additional changes to your code or method of development. nodemon is a replacement wrapper for node. To use nodemon, replace the word node on the command line when executing your script. you can install node using following command.

$ npm i -D nodemon

 10 . Now you can running using following command:

$ npm run start:dev


Step 2 - Setup Router in express application

  1. Setup an Express application with a router in index.ts. First create router folder in root directory, under router folder create index.ts for router. 
// router/index.ts
import
express from "express";
const router = express.Router();

export default (): express.Router => {}

  2. import router/index.ts in startup index.ts.

 import router from './router';
app.use('/api', router());


Step 3 - Setup database

for database, i am using mongoDB to store user detail. To connet mongoDB, I am using mongoose  package. to install mongoose in node js application. 

you can used following command

$ npm i mongoose

// we are using typeScript then we have to install dev dependence
$ npm i @types/mongoose

To connect mongoDB we have to implement some code in statup index.ts

// index.ts
import mongoose from 'mongoose';

const MONGO_URL:any = "mongodb://localhost:27017";
mongoose.Promise = Promise;
mongoose.connect(MONGO_URL);
mongoose.connection.on('error', (error: Error) => console.log(error));

Step 4 Add User Schema

Add user schema and model  for a user document in MongoDB

Schema Fields:

  • username: A required, unique String field for the user's username.
  • email: A required, unique String  field for the user's email.
  • name: A required String  field for the user's full name.
  • password: A required String filed for the user's password.
  • createdAt: A Date field with a default value of Date.now.
  • updateAt: An optional Date field intended to track when the document was last updated.
import mongoose from "mongoose";
import bcrypt from 'bcryptjs';

const UserSchema = new mongoose.Schema({
    username: { type: String, required: true, unique:true },
    email: { type: String, required: true, unique:true },
    name:{type:String, required:true},
    password:{type:String, required:true},
    createdAt: {
        type: Date,
        default: Date.now 
    },
    updateAt:{
        type:Date,
        required:false
    },
});

export const UserModel = mongoose.model("User", UserSchema);

export const getUserByEmail = (email: string) => UserModel.findOne({ email });
export const getUserByUsername = (phone_number:string) => UserModel.findOne({username});
export const getUserById = (id: string) => UserModel.findById(id);
export const createUser = (values: Record<string, any>) => new UserModel(values).save().then((user => user.toObject()));
export const comparePassword = (password:string, dbPassword:string)=>{
    return bcrypt.compareSync(password, dbPassword);
}

Step 4 Create Controller for API 

1. Resigter User

create new folder in root directory, which name is controllers. Under controller create new file authentications.ts. 

export const register = async (req: express.Request, res: express.Response) => {
    try {
     
const { email, username, password, name } = req.body;

if (!email || !username || !password || !name) {
    return res.status(StatusCodes.BAD_REQUEST).json({ error: 'All fields (email, password, name) are required' });
}

const existingUserByEmail = await getUserByEmail(email);
if (existingUser) {
    return res.status(StatusCodes.BAD_REQUEST).json({ message: 'User with this email already exists' });
}

const existingUserUsername = await getUserByPhone(username);
if(existingUserPhone){
    return res.status(StatusCodes.BAD_REQUEST).json({message:'User with this Username already exists'});
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = await createUser({
    email,
    name,
    username,
    password: hashedPassword
});  

        return res.status(StatusCodes.CREATED).json({
            message: 'User registered successfully',
            user: {
                email: user.email,
                name: user.name,
                username: user.username,
                createdAt: user.createdAt,
            }
        });
    } catch (error) {
        console.error('Registration error:', error);
        return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: 'Internal server error' });
    }
};
  1. Input fields and Validate inputs:

Extract all body request and checks if any of the required fields are missing. if missing then return 400 Bad Request with error message.

const { email, username, password, name } = req.body;

if (!email || !username || !password || !name) {
    return res.status(StatusCodes.BAD_REQUEST).json({ error: 'All fields (email, password, name) are required' });
}

 2. check for existring user:

Check is a user provided email and username already exists. is exists then reutrn 400 Bad Request with error message.

const existingUserByEmail = await getUserByEmail(email);
if (existingUser) {
    return res.status(StatusCodes.BAD_REQUEST).json({ message: 'User with this email already exists' });
}

const existingUserUsername = await getUserByPhone(username);
if(existingUserPhone){
  return res.status(StatusCodes.BAD_REQUEST).json({message:'User with this Username already exists'});
}

3. Hash Password and Create User

If no duplicate users are found, the function hashes then password using bcrypt with salt round of 10, then create a new user with createUser by storing details like email, name username and password.

const hashedPassword = await bcrypt.hash(password, 10);
const user = await createUser({
    email,
    name,
  username,
    password: hashedPassword
});

4. Return Success Response

Once the user is created then return response with 201 Created status.

return res.status(StatusCodes.CREATED).json({
    message: 'User registered successfully',
    user: {
        email: user.email,
        name: user.name,
        username: user.username,
        createdAt: user.createdAt,
   
    }
});

2. Login User

export const login = async (req: express.Request, res: express.Response)=>{

    try{

        const {email, password} = req.body;

        if(!email || !password){

            return res.status(401);

        }

        const user = await getUserByEmail(email);

        if(!user){

            return res.status(StatusCodes.BAD_REQUEST).json({message:"user not found"});

        }

        if(user && user.password){

            const checkPassword = comparePassword(password, user.password);

            if(!checkPassword){

                return res.status(StatusCodes.BAD_REQUEST).json({message:"Invalid password"});

            }

            const token = generateToken({id:user._id.toString(),

                username:user.username,

                role:(user.role as IRole).name

            });

            let data = {

                token:token,

                user:{

                    id:user._id,

                    name:user.name,

                    email:user.email,

                    createdAt:user.createdAt,
                    image:user.image?user.image:''

                }

            }

            return res.status(StatusCodes.OK).json(data);

        }  

       

    }catch(error:any){

        console.log(error);

        return res.status(StatusCodes.BAD_REQUEST).json({message:error.message});

    }

}

// generate token

const generateToken = (user:any):string=>{
    return jwt.sign(user, SECRET_KEY, {expiresIn:'1h'});
}
  1. Validate Login credentails:

     Extracts email and password from the request body. If either fields is missing it. return a 401 Unauthorized response.

const { email, password } = req.body;

if (!email || !password) {
    return res.status(401);
}

2. Fetch User details:

Find the user by email using getUserByEmail. If no user found, its returns a 400 Bad Request response with a message indicating that the user was not found.

const user = await getUserByEmail(email);
if (!user) {
    return res.status(StatusCodes.BAD_REQUEST).json({ message: "user not found" });
}
3. Verify Password:
If user exists, then compare password to stored password using comparePassword function. If the password does not match, then return a 400 Bad Request  reponse with an "Invalid Password" message.

if (user && user.password) {
    const checkPassword = comparePassword(password, user.password);
    if (!checkPassword) {
        return res.status(StatusCodes.BAD_REQUEST).json({ message: "Invalid password" });
    }
4. Generate JWT Token:
If the password is correct, then generate JWT Token with generateToken.
const token = generateToken({
    id: user._id.toString(),
    username: user.username,
    role: (user.role as IRole).name
});
5. Generate JWT Token.
Create helper function for generate JWT token.
const generateToken = (user: any): string => {
   return jwt.sign(user, SECRET_KEY, { expiresIn: '1h' });
}
6. Create router for login and register.
Create new file in under router folder, which name is authenticate.ts. And add login register router
import express from 'express';

import { login, register } from '../controllers/authentications';
import { isAuthenticated} from '../middlewares';


export default (router: express.Router) => {
  router.post('/auth/register', register);
  router.post('/auth/login', login);
    router.get('/profile', isAuthenticated, userProfile);
 
}
Import authentcate.ts router file in router/index.ts
import authentication  from "./authentication";
const
router = express.Router();

export default (): express.Router => {

    authentication(router);

}

3. Create Meddlewares for validate User.

when user hit api, which api is authenticate API then check JWT token is valid or not. 

export const isAuthenticated = async (req:express.Request, res:express.Response, next:express.NextFunction)=>{
    try{
        const token = req.headers.authorization?.split(" ")[1];
        if(!token){
            return res.status(StatusCodes.UNAUTHORIZED).json({error:'No token provided'});
        }
        req.user = verifyToken(token);
        return next();
    }catch(error){
       
        return res.status(StatusCodes.UNAUTHORIZED).json({error:'Invalid token'});
    }
}

// verify token
const verifyToken = (token:string):any =>{
    return jwt.verify(token, SECRET_KEY) as JwtPayload;
}

Create new Api, which api is authenticated

router.get('/profile', isAuthenticated, userProfile);

Comments

Login to leave a comment.

Build Software Application with Impact Hive