Token-based Authentication and private routes in JavaScript using Express Node MongoDB

Token-based Authentication and private routes in JavaScript using Express Node MongoDB

ยท

5 min read

In this blog, we are going to discuss how to set up token-based Authentication, handling signup routes, login routes, and private routes in JavaScript.

Packages used:

Topics discussed in this blog:

  • What is Authentication
  • User Schema
  • SignUp Handler
  • Login Handler
  • Auth Validation

What is Authentication

In simple terms, Authentication means checking whether a particular request to the server is from the original user or not.

What is a Server?

For implementing a REST API with all the above-discussed functionalities, we have to set up an ExpressJS server.

In computing, a server is a piece of computer hardware or software (computer program) that provides functionality for other programs or devices. Once the entire code is done we can host this particular code onto a PAAS(Platform as a Service) platform like Heroku.

Server setup

const express = require("express");
require("dotenv").config();
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 5000;
app.get("/",(req,res)=>{
  res.status(200).json({
    success:true,
    message:"Welcome to the backend of Authentication Blog"
  })
});
app.listen(PORT, () => {
  console.log(`server started at ${PORT}`);
});

Here we are using express package for setting up the server and handling different API endpoints on the backend. "dotenv" to access different key-value pairs present in the .env file.

User Schema

In this particular User Schema, we have only three key-value pairs. i.e., user email, name, and password.

const mongoose = require("mongoose");
const { Schema, model } = mongoose;
const opts = { toJSON: { virtuals: true } };
const userSchema = Schema(
  {
    name: {
      type: String,
      required: "Add a name",
    },
    email: {
      type: String,
      required: "Add an email",
    },
    password: {
      type: String,
      required: "Add password",
    },
  },
  opts
);
module.exports = model("User", userSchema);
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");

User Signup Handler

The main steps in signup handling are

  1. Check whether a user exists with this particular email, If yes return a response that 'email is already registered '. If no go to step 2.

    Saving a text password we don't do that here.gif

  2. Since we don't save passwords as plain text we have to encrypt the user-given password. For encryption we use bcrypt.
  3. Then save the hashed password as the user's password in DB.

Code :

const userSignup = async (req, res) => {
  const { email, password, name } = req.body;
  try {
    const userExists = await User.findOne({ email: email });
    if (userExists) {
      res.status(500).json({
        success: false,
        message: "Email is already registered",
      });
    }
    const hashPassword = await bcrypt.hash(password, 12);
    const newUser = new User({ email, name, password: hashPassword });
    await newUser.save();
    res.status(200).json({
      success: true,
      message: "User registered successfully",
    });
  } catch (err) {
    res.status(500).json({
      success: false,
      message: "Error in signup",
    });
  }
};

User login Handler

The main steps in Login Handling are :

  1. Check if the user's email is registered or not. If not registered return a response that "Email is not registered".If the email exists in DB move to step 2
  2. Comparing the user input password with the hashed password saved in DB. It can be done with help of bcrypt.
  3. If the comparison result is false return a response that "Invalid credentials", else move to step 4.
  4. Since in this blog we are discussing token-based Authentication, the server returns a token in the response if password comparison is true. A token can be generated using the json-web-token library.
  5. Things to keep note while generating a user-specific token never put user details like email, password in the token and have an expiration time of at least 24 hours.
  6. On successfully generating a token, it can be sent as a response. So that client can use that token for performing further requests.

Code :

const userLogin = async (req, res) => {
  const { email, password } = req.body;
  try {
    const userData =await User.findOne({ email: email });
    if (!userData) {
      res.status(500).json({
        success: false,
        message: "Email doesnt exists",
      });
    }
    const isValidPassword = await bcrypt.compare(password, userData.password);
    if (!isValidPassword) {
      res.status(500).json({
        success: false,
        message: "Invalid Credentials",
      });
    }
    const token = jwt.sign({ userId: userData.id }, process.env.KEY, {
      expiresIn: "24h",
    });
    res.status(200).json({
      success: true,
      userToken: token,
    });
  } catch (err) {
    res.status(500).json({
      success: false,
      message: "Error in Login process",
    });
  }
};

Adding routes

SignUp Routes :

app.post("/signup",userSignup)

Login Route:

app.post("/login",userLogin);

Auth Validation :

This particular functionality can be written as a middleware so that it can be reused for all the private routes. The main steps in this functionality are :

  1. Accessing token for the request headers, If the token doesn't exists return a response that "user is not logged in". 2.If a token exists then verify it using json-web-token's verify method.
  2. If verification is successful proceed to next function using next() method.

Code :

const jwt = require("jsonwebtoken");
const authValidator = async (req, res, next) => {
  try {
    const token = req.headers.authorization;
    if (!token) {
      res.status(401).json({
        success: false,
        message: "User is not loggedIn",
      });
    }
    const { userId } = jwt.verify(token, process.env.KEY);
    req.userId = userId;
    next();
  } catch (err) {
    res.status(401).json({
      success: false,
      message: "Error in validating User",
    });
  }
};

Adding public and private routes

Unauthenticated users cannot access private routes. not allowed.webp Public routes can be accessed by any user. Private routes are accessed by only an authenticated user. By authenticated user, I mean request headers must contain an Authorization header with Token as value.

//public route 
app.get("/public",(req,res)=>{
res.status(200).json({
  success:true,
  message:"Public route"
})
//private route
app.get("/private",authValidator ,(req,res)=>{
res.status(200).json({
  success:true,
  message:"Private route"
})

While implementing this add a .env file to the folder and place two key-value pairs

  1. KEY = require('crypto').randomBytes(256).toString('base64')
  2. DB_URL

So using the above code we can implement Token-based Authentication in JavaScript.

References Code Bcrypt , json-web-token

Thank you for reading ๐Ÿ™

I hope you found this article useful.

If you enjoyed this article or found it helpful, give it a thumbs-up ๐Ÿ‘

Feel free to connect ๐Ÿ‘‹

Twitter | Instagram | LinkedIn

ย