Complete User Authentication with JWT token | Learn MERN Stack

Warning -  Read this article with a cup of tea. It gonna be long , but informative! 

Things Covered in this article : 


Step 1 : Creating Folders 

  - Create 2 folder 

      - backend/ routes

                userRoutes.js

                --------------------

const express = require("express");
const { registerUser } = require("../controllers/userControllers");

const router = express.Router();

router.route("/").post(registerUser);

module.exports = router;


     - backend/controllers    

            userControllers.js

            -------------------------

const registerUser = async (reqres=> {
  const { nameemailpasswordpic } = req.body;

  res.json({
    name,
    email,
  });
};

module.exports = { registerUser };


Step 2: Add new endpoint to server.js file - 

server.js

----------

const express = require("express");
const dotenv = require("dotenv");
const notes = require("./Data/notes");
// const { connect } = require("mongoose");
const connectDB = require("./config/db");
const app = express();
const userRoutes = require("./routes/userRoutes");
dotenv.config();

connectDB();
app.use(express.json());

app.get("/", (reqres=> {
  res.send("API is running");
});

app.get("/api/notes", (reqres=> {
  res.send(notes);
});

app.use("/api/user"userRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORTconsole.log(`server started at ${PORT}`));


Step 3 : Defining Schema & Models

Schema - The Schema allows you to define the fields stored in each document along with their validation requirements and default values.

Schemas are then "compiled" into models using the mongoose.model() method. Once you have a model you can use it to find, create, update, and delete objects of the given type.

Models - Models are fancy constructors compiled from Schema definitions. An instance of a model is called a document. Models are responsible for creating and reading documents from the underlying MongoDB database.

// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
The first argument is the singular name of the collection that will be created for your model (Mongoose will create the database collection for the above model SomeModel above), and the second argument is the schema you want to use in creating the model.

create backend/models 

> Create a folder - backend/models

         - userModels.js (filename)

userModel.js:

-------------

const mongoose = require("mongoose");

var Schema = mongoose.Schema;

var userSchema = new Schema(
  {
    name: {
      type: String,
      required: true,
    },
    email: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: Boolean,
      required: true,
    
    },
    isAdmin: {
      //just in case if we need
      type: Boolean,
      required: true,
      default: false,
    },
    pic: {
      type: String,
      required: true,
      default:
        "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
    },
  },
  {
    timestamps: true,
  }
);

const User = mongoose.model("User"userSchema);

module.exports = User;

Step 4 : Logic for creating a user profile and appending in database

> First install few dependencies 

  - npm i bcryptjs express-async-handler jsonwebtoken


updating userController.js

---------

const userModel = require("../models/userModel");
const asyncHandler = require("express-async-handler");
const User = require("../models/userModel");

//taking input from user
const registerUser = asyncHandler(async (reqres=> {
  const { nameemailpasswordpic } = req.body;

  //checking if the user is already present (using email)
  const userExists = await User.findOne({ email });

  if (userExists) {
    res.status(400);
    throw new Error("user Already exists");
  }

  //creating a new user if the user does not exist
  const user = await User.create({
    name,
    email,
    password,
    pic,
  });

  //showing user data for frontend
  if (user) {
    res.status(201).json({
      _id: user.id,
      name: user.name,
      email: user.email,
      isAdmin: user.isAdmin,
      pic: user.pic,
    });
  } else {
    res.status(400);
    throw new Error("error occured");
  }
});

module.exports = { registerUser };

Testing : 

Go to postman/thunder client and checking :

Creating post request - 

> URL - http://localhost:7000/api/user

Body > Json : 

{

    "name":"him shekh",

    "email":"aaaaaa@gmail.com",

    "password":"dab",

    "pic":"him.png"

}

> Press SEND button

Getting response : 

Status: 201 

User-Created!


Now , go to MongoDB > Clustor > Collectinon 

and see for yourself.












HURRAY! You have successfully created a user profile and added it to database.


Step 5 : Encrypting password

If you have seen the snapshot, the passwork is in letter, which can be hacked.

Let encrypt it.


code for encrypting : 

----------------------

userSchema.pre("save"async function (next) {
  if (!this.isModified("password")) {
    next();
  }
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.passwordsalt);
});

        

code for decrypting : 

-------------------------

//for decrypting the password
userSchema.methods.matchPassword = async function (enteredPassword) {
  //enteredPassword - password which user entered in frontend
  //this.passwod - password which is present is database
  return await bcrypt.compare(enteredPassword.this.password);
};

add this in userModel.js-

-----------------------------

const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");

var Schema = mongoose.Schema;

var userSchema = new Schema(
  {
    name: {
      type: String,
      required: true,
    },
    email: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
    isAdmin: {
      //just in case if we need
      type: Boolean,
      required: true,
      default: false,
    },
    pic: {
      type: String,
      required: true,
      default:
        "https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
    },
  },
  {
    timestamps: true,
  }
);

//for enrypting the password
userSchema.pre("save"async function (next) {
  if (!this.isModified("password")) {
    next();
  }
  const salt = await bcrypt.genSalt(10); //the higher the value, more secure the password
  this.password = await bcrypt.hash(this.passwordsalt);
});

//for decrypting the password
userSchema.methods.matchPassword = async function (enteredPassword) {
  //enteredPassword - password which user entered in frontend
  //this.password - password which is present is database
  return await bcrypt.compare(enteredPassword,this.password);
};

const User = mongoose.model("User"userSchema);

module.exports = User;


Again test in postman and boom! Password is encrypted now.






Step 6 : Error Handling - 

> create a new folder backend/middleware > create a flile - errorMiddleware.js

errorMiddleware.js:

---------------------------

const notFound = (reqresnext=> {
  const error = new Error(`Not Found - ${req.originalUrl}`);
  res.status(404);
  next(error);
};

const errorHandler = (errreqresnext=> {
  const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
  res.status(statusCode);
  res.json({
    message: err.message,
    stack: process.env.NODE_ENV === "production" ? null : err.stack,
  });
};

export { notFounderrorHandler };

    

server.js:

--------------

const express = require("express");
const dotenv = require("dotenv");
const notes = require("./Data/notes");
const { connect } = require("mongoose");
const connectDB = require("./config/db");
const app = express();
const userRoutes = require("./routes/userRoutes");
const { notFounderrorHandler } = require("./middlewares/errorMiddleware");
dotenv.config();

connectDB();
app.use(express.json());

app.get("/", (reqres=> {
  res.send("API is running");
});

app.get("/api/notes", (reqres=> {
  res.send(notes);
});

app.use("/api/users"userRoutes);

app.use(notFound);
app.use(errorHandler);

const PORT = process.env.PORT || 5000;
app.listen(PORTconsole.log(`server started at ${PORT}`));


Step 7 : Creating Login - 

userRoutes : 

------------------

const express = require("express");
const { registerUserauthUser } = require("../controllers/userControllers");

const router = express.Router();

router.route("/").post(registerUser);
router.route("/login").post(authUser); //this is added

module.exports = router;


userControllers.js : 

----------------------

const userModel = require("../models/userModel");
const asyncHandler = require("express-async-handler");
const User = require("../models/userModel");

//creating a user (signup login)
//taking input from user
const registerUser = asyncHandler(async (reqres=> {
  const { nameemailpasswordpic } = req.body;

  //checking if the user is already present (using email) , no need to create account
  const userExists = await User.findOne({ email });

  if (userExists) {
    res.status(400);
    throw new Error("user Already exists");
  }

  //creating a new user if the user does not exist
  const user = await User.create({
    name,
    email,
    password,
    pic,
  });

  //showing user data for frontend
  if (user) {
    res.status(201).json({
      _id: user.id,
      name: user.name,
      email: user.email,
      isAdmin: user.isAdmin,
      pic: user.pic,
    });
  } else {
    res.status(400);
    throw new Error("error occured");
  }
});

//this is added
//login
const authUser = asyncHandler(async (reqres=> {
  const { emailpassword } = req.body;

  const user = await User.findOne({ email });

  if (user && (await user.matchPassword(password))) {
    res.json({
      _id: user.id,
      name: user.name,
      email: user.email,
      isAdmin: user.isAdmin,
      pic: user.pic,
    });
  } else {
    res.status(401);
    throw new Error("Invalid Email or Password");
  }
});
module.exports = { registerUserauthUser };


Step 8 : Installing JWT Token - 

npm i jsonwebtoken

> Create a new folder - backend/utils

> Create new file inside it - generateToken.js

> JSON Web Tokens

JSON web token (JWT), pronounced "jot", is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. Again, JWT is a standard, meaning that all JWTs are tokens, but not all tokens are JWTs.

Because of its relatively small size, a JWT can be sent through a URL, through a POST parameter, or inside an HTTP header, and it is transmitted quickly. A JWT contains all the required information about an entity to avoid querying a database more than once. The recipient of a JWT also does not need to call a server to validate the token.

Use

JWTs can be used in various ways:

  • Authentication: When a user successfully logs in using their credentials, an ID token is returned. According to the OpenID Connect (OIDC) specs, an ID token is always a JWT.

  • Authorization: Once a user is successfully logged in, an application may request to access routes, services, or resources (e.g., APIs) on behalf of that user. To do so, in every request, it must pass an Access Token, which may be in the form of a JWT. Single Sign-on (SSO) widely uses JWT because of the small overhead of the format, and its ability to easily be used across different domains.

  • Information Exchange: JWTs are a good way of securely transmitting information between parties because they can be signed, which means you can be sure that the senders are who they say they are. Additionally, the structure of a JWT allows you to verify that the content hasn't been tampered with.

generateToken.js

const jwt = require("jsonwebtoken");

const generateToken = (id=> {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: "30d",
  });
};

module.exports = generateToken;

.env : 

JWT_SECRET= 12345


userController.js

---------------------

Add token in the response of registerUser and authUser







Thank you for reading. Have a nice day!

0 Comments