Authenticate Like a Pro: A Beginner's Guide to Node.js Authentication

Authenticate Like a Pro: A Beginner's Guide to Node.js Authentication

ยท

10 min read

Authentication is the process of verifying the identity of a user or entity attempting to access a system or resource. It is a fundamental aspect of security in web development and is essential for ensuring that only authorized individuals or entities are granted access to sensitive information, protected resources, or specific functionalities within an application.

Let's create a basic Authentication system in Node.js, we will be using the following libraries for this purpose: bcrypt, dotenv, express, jsonwebtoken, mysql2, sequelize, validatorjs.

STEP 0: Creating an empty Nodejs repository

Go to your desired directory and run the below command to setup an empty directory with package.json

npm init -y

STEP 1: Creating a server

To create an express server we have to install express

STEP 1.1: Installing Express

npm i express

the above command will install the express library to your project.

STEP 1.2: Setting up the server

Create a new javascript file - you can name it anything, for the sake of simplicity let's call it server.js A node server can be set up by just three lines of code as

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

app.listen(3000, () => {
    console.log(`App is up and running at port 3000`);
})

STEP 1.3: Install and configure dotenv

we will be using some high confidentiality passwords/keys in the project, so it will be beneficial to not save them as such in the project but to create a non-versioned file and store them in that.

to have this kind of feature we have to install dotenv

npm i dotenv

after that create a .env file in your project and for now, add the value of PORT there, we will use the value of port from there.

.env will look something like this, it stores data in key-value format.

PORT=1667

now to use this value of PORT from .env, we have to configure dotenv in the server.js file and use the value.

const express = require("express");
// This line is setting up the .env so that we can use it in our code
require("dotenv").config();

const app = express();
const port = process.env.PORT // This is how we can use it

app.listen(port, () => {
    console.log(`App is up and running at port ${port}`);
})

STEP 2: Sequelize and MySQL

STEP 2.1: Installing Sequelize and mysql2

we will be downloading two npm packages that will help us use MySQL with nodejs, they are - sequelize and mysql2

npm i sequelize mysql2

STEP 2.2: Creating a database on the local machine

Depending on how you are managing the SQL Server on your machine, the steps for creating a new database may vary.

If you are running SQL Server on Windows using XAMPP, you can create a new database by following the instructions provided in the link.

However, if you are using Ubuntu or Mac, you will need to follow these steps

  1. Open Terminal and open your SQL server using this command

      mysql -u root -p
    
  2. You will be prompted to write a password, write your SQL server password, and you will be inside the SQL server.

  3. now create a database using this command

      CREATE DATABASE jwt-playground;
    

STEP 2.3: Configuring Sequelize

open .env and add the database credentials that we have just created above, .env will look something like this

PORT=1667

DATABASE=jwt-playground
USERNAME=root
HOST=127.0.0.1
DIALECT=mysql
PASSWORD=**********

Create a new folder named configs. Inside the configs folder, create a new file named index.js. In this file, you will set up sequelize so that it can be used throughout the project.

const { Sequelize } = require('sequelize');

// creating a new sequelize object by the name of DB
const db = new Sequelize(process.env.DATABASE, process.env.USERNAME, process.env.PASSWORD, {
    host: process.env.HOST,
    dialect: process.env.DIALECT
})

// authenticating the db, so that it can be connected to the local DB // that we have made.
try {
    db.authenticate();
    console.log(`database connected ๐Ÿ’ช`);
} catch(error) {
    console.error(`database connection failed: ${error}`)
}

module.exports = db;

STEP 3: Models

Models are an essential part of Sequelize. They represent the structure of a table for a particular entity.

STEP 3.1: Creating the User Model

create a file name User.js inside model directory and add the following columns in it.

const { DataTypes } = require('sequelize');
const db = require("../config")

const User = db.define('User', {
  name: {
    type: DataTypes.STRING,
    allowNull: false
  },
  email: {
    type: DataTypes.STRING,
    unique: true,
    allowNull: false
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false
  }
}, {
  tableName: 'users',
  timstamps: true
});

module.exports = User;

STEP 3.2: Using the Models

Next, create another file called index.js in the same folder as your models. In this file, we will import all of our models and sync them with the database. The contents of this file should look something like this

const User = require("./User");

User.sync();

We now need to modify the code in server.js to only call the index.js file of the models folder. That's all that is required.

require("dotenv").config();

const express = require("express");
// requiring index.js from models folder, which in turn sync the 
// tables with all the existing database
require("./models/index");

const app = express();
app.use(express.json());
const port = process.env.PORT

app.get("/", (req, res) => {
    res.send("Hello World");
})

app.listen(port, () => {
    console.log(`App is up and running at ${port}`);
})

STEP 4: Creating Routes and Controllers

STEP 4.1: Creating the Controller

The controller is the place where the basic logic resides, so create a controller folder and inside that folder create a file named AuthController.js

// These are the basic skeleton of all the methods that we will be 
// writing now, i.e. to login and signup for users
const login = async (req, res) => {}

const signup = async (req, res) => {}

module.exports = {
    login: login,
    signup: signup,
}

STEP 4.2: Creating the Route

We will now create a new folder called routes. Inside this folder, we will create a new file named AuthRoute.js. This file will contain all the APIs related to Auth. The code for this file should look something like this

const express = require("express");
// fetching router from the express
const router = express.Router();

// fetching all the controller methods, to call and create API for 
// each one of them
const { 
    login, 
    signup,
} = require("../controllers/AuthController");

router.post("/login", login);
router.post("/signup", signup);

// exporting the router as hashiraRoute so that it can be easily 
// identified in server.js
module.exports = {
    authRoute: router
}

STEP 4.3: Server.js modification

server.js will look something like this -

require("dotenv").config();
const express = require("express");
const { authRoute } = require("./routes/authRoute");
require("./models/index");

const app = express();
const port = process.env.PORT

app.use(express.json());
// imported the route here - all the apis will have a prefix of 
// auth.
app.use("/auth", authRoute);

app.listen(port, () => {
    console.log(`App is up and running at ${port}`);
})

STEP 5: Validations

Validations are used to ensure that the data being sent to or retrieved from the server is accurate, complete, and conforms to a set of predefined rules or criteria.

We will be using validatorjs in our project for this. It can be installed as follows -

npm i validatorjs

To use this we will create a folder named validators, in that folder we will create separate files for each API, the getAll API would not need any validations, so skipping that.

STEP 5.1: The Login Validator

create a file named loginValidator.js, and define the rules for data of logging in.

const validator = require("validatorjs");

// creating a method which will take the incoming data and based on 
// rules, it will tell whether it is passed or failed. 
const loginValidator = (data) => {
    return new validator(data, {
        email: "required|email",
        password: "required|string"
    })
}

module.exports = loginValidator

STEP 5.2: The Signup Validator

create a file named signupValidator.js, and define the rules for signing up a new user.

const validator = require("validatorjs");

// creating a method which will take the incoming data and based on 
// rules, it will tell whether it is passed or failed. 
const signupValidator = (data) => {
    return new validator(data, {
        name: "required|string",
        email: "required|email",
        password: "required|string"
    })
}

module.exports = signupValidator

STEP 6: Filling up the Controllers

STEP 6.1: Installing Json Web Token

we will be downloading two npm packages that will help us use JWT which are jsonwebtoken and bcrypt.

we will be using bcrypt to hash and compare the password.

npm i jsonwebtoken bcrypt

STEP 6.2: Editing the .env

create a new entry in .env and create a AUTH_KEY variable, add a random string into that, it will be used to create the JWT token.

PORT=1667

AUTH_KEY=081a328fcd87e55de00f84bec

DATABASE=jwt-playground
USERNAME=root
HOST=127.0.0.1
DIALECT=mysql
PASSWORD=password

STEP 6.3: The Signup Method

This method will create a new User in the database. The following steps should be followed

  1. Get the request object from the API.

  2. Validate the request object. If it is incorrect, throw an error.

  3. Encrypt the password using bcrypt.

  4. Create a new database entry with the validated request object.

  5. Create a JWT token for that particular User.

  6. Return the response for the newly created User along with generated JWT.

const signup = async (req, res) => {
    const payload = req.body;

    // everything wrapped inside try - catch to catch the error at 
    // each point
    try {
        const validator = await signupValidator(payload)
        if (validator.fails()) {
            // return error code 422 and the errors along with it
            return res.status(422).json({ 
                error: validator.errors.all() 
            })
        }

        // code will be here only if passed the above validation

        // creating the database entry using the model created, 
        // we are hashing the password using bcrypt before saving 
        // it into the database.

        // It takes two parameters - the password and
        // salt (number of rounds of hashing needed)
        const user = await User.create({
            name: payload.name,
            email: payload.email,
            password: await bcrypt.hash(payload.password, 10)
        });

        // generating auth token using jsonwebtoken library
        // It takes two parameters - the user object which 
        // acts as the signature and the auth key using which 
        // it generates
        const token = jwt.sign({ user }, process.env.AUTH_KEY)
        // return status code 200, and the newly created hashira
        return res.status(200).json({ data: user, token: token })
    } catch(e) {
        // return status code 500, and the error something went wrong
        return res.status(500).json({ error: `something went wrong` })
    }
}

STEP 6.4: The Login Method

This method will log a user into the system. The following steps should be followed

  1. Get the request object from the API.

  2. Validate the request object. If it is incorrect, throw an error.

  3. Check if the email exists in the database, if not throw an error

  4. Check if the password matches the one saved in DB, if not throw an error

  5. Create a JWT token for that particular User.

  6. Return the response for the User along with generated JWT.

const login = async (req, res) => {
    const payload = req.body;

    try {
        const validator = await loginValidator(payload)
        if (validator.fails()) {
            return res.status(422).json({ 
                error: validator.errors.all() 
            })
        }

        // checking if the user with email exists in database
        const user = await User.findOne({
            where: {
                email: payload.email
            }
        })

        // throwing error if it does not
        if (!user) {
            return res.status(404).json({ 
                error: `User with this email does not exist` 
            })
        }

        // comparing the entered password with the one 
        // saved in database.
        // The compare method takes two params - 
        // the incoming password and the existing hashed password
        if (
         !(await bcrypt.compare(payload.password, user.password))
         ) {
            return res.status(400).json({ 
                error: `Password entered is wrong` 
            })
        }

        const token = jwt.sign({ user }, process.env.AUTH_KEY)
        return res.status(200).json({ data: user, token: token })
    } catch(e) {
        return res.status(500).json({ error: `something went wrong` })
    }
}

STEP 7: Time to test

STEP 7.1: The Signup Method

STEP 7.1.1: Success

STEP 7.1.2: Failure

STEP 7.2: The Login Method

STEP 7.2.1: Success

STEP 7.2.2: Failure

STEP 7.2.3: Failure

That's all, folks! I hope this tutorial gave you a basic idea of how to use and create authentication in using JWT in node.js.

Thank you for reading! Please feel free to follow me on social media.

Did you find this article valuable?

Support Adesh Khanna by becoming a sponsor. Any amount is appreciated!

ย