MERN Stack - Authentication and Deployment - Part 1/3

Wed Oct 27 2021

This is the first tutorial on MERN stack authentication and deployment series.

First we will create our project folder so navigate to your desired directory and open terminal there.

mkdir mern-auth

It will create a directory mern-auth feel free to name it whatever you like. Navigate into project directory and in terminal type

npm init -y

It will create a package.json file. Now create an entry file index.js. We will install a few dependencies for our project. So in terminal type

npm install express mongoose bcryptjs jsonwebtoken dotenv

  • bcryptjs for password hashing as we will not store plain text password into our database.

  • jsonwebtoken for authentication / authorization.

  • dotenv for environment variables.

I will also install a dev dependency to constantly watch changes in our backend

npm install -D nodemon

Open package.json file and replace existing scripts with

"scripts": {
   "start": "node index.js",
   "server": "nodemon index.js
},

Open project in code editor, go to index.js file and paste the following

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

app.use(express.json())

const PORT = process.env.PORT || 5000

app.listen(PORT, () => console.log("Server is running"))

Here we brought express and invoke it. After that we have added a body parser express.json. We then defined a PORT variable, for production PORT value will be different and in development we will use port 5000 and finally our app is listening on PORT which we specified earlier.

We will use mongodb as our database so go to mongodb and create an account if you already dont have one or login. If its your first time using mongodb then you will have to build a cluster first.

Click Build a Cluster click Continue on account setup page, you will have to select pricing plan. We will work on free plan so in Shared Clusters click Create a cluster. Now you will have to select cloud provider and region so select any one from aws / Google Cloud / Azure as cloud provider and select your nearest region. Finally click Create Cluster. It will take a few minutes to build.

Once its done now its time to create database. Click connect. Provide username and password and click Create Database User. After that we will select a connection method so click on Choose a connection method then Connect your application. Make sure nodejs is selected as driver, then copy the database url.

Go to code editor, create a .env file at the root level of your project. Inside it add

MONGO_URI=<paste your database url>

Replace <password> with your database password. Replace <dbname> name it whatever you like. In index.js file after second line paste the following

const mongoose = require("mongoose")
require("dotenv").config()

// Connect DB
mongoose
  .connect(process.env.MONGO_URI, {
    useCreateIndex: true,
    useNewUrlParser: true,
    useFindAndModify: false,
    useUnifiedTopology: true,
  })
  .then(() => console.log("mongoDB is connected"))
  .catch(err => console.log(err))

Here we brought mongoose and dotenv and then we connected to our database passing MONGO_URI as first argument to mongoose.connect and as second argument we have passed a few options required by mongoose to avoid potential warnings / errors. In your terminal run

npm run server

you should see Server is running and mongoDB is connected in terminal.

Now we will create User model to specify the fields users will have. Create a folder at root level models and inside it create a file user.js and paste the following code.

const mongoose = require("mongoose")

const userSchema = new mongoose.Schema(
  {
    name: { type: String },
    email: { type: String },
    password: { type: String },
  },
  { timestamps: true }
)

module.exports = mongoose.model('User', userSchema)

So user will have three fields name, email and password and all of them are of type String and timestamps: true will automatically create createdAt and updatedAt fields and then we exported this model so that we can use it in our app.

Now at root level create a folder routes and create a file user.js inside it. Go to index.js and after app.use(express.json()) add

app.use('/auth', require('./routes/user'))

This /auth will be the base path so that in our routes/user.js we dont have to write /auth/register and /auth/login, we will only write /register and /login and /auth will be added to it because of the above line. The second argument is the path to the file.

Let's start working on user registration. In routes/user.js file, paste the following

const router = require("express").Router()
const bcrypt = require("bcryptjs")
const User = require("../models/user")

// Register user
router.post("/register", async (req, res) => {
  const { name, email, password } = req.body
  try {
    let user = await User.findOne({ email })
    if (user) {
      return res.status(400).json({ error: "User already exists" })
    }
    const hashedPassword = await bcrypt.hash(password, 10)
    user = new User({ name, email, password: hashedPassword })
    await user.save()
    res.status(201).json({ message: "User created successfully" })
  } catch (err) {
    console.log(err)
  }
})

module.exports = router

At the top we imported express and initialized router instance. We also imported bcryptjs and user model. Creating a new user will be a post request and route for user registration will be /auth/register, remember we required this file in index.js and the base path is /auth so here first argument to the post method is path /register and second argument is an asynchronous function which takes request and response as arguments.

We destructured name, email and password from req.body. Inside try block, before creating a new user we have to check if there is a user already registered with the email provided by the user. If email is already registered then we will return an error with status code of 400 (bad request) along with the message.

If email is not already registered then we will proceed with registration. We will hash password with bcryptjs. hash method takes two arguments, plain text password provided by user and salt. Salt is a length to generate random string. After that we have created a new instance of our User model and provided name, email and password. Remember password here will be the hashed password, not the plain text password. Finally we used save method to store the data into mongodb and at the end we exported router so that we can import this file into index.js which we already did like app.use('/auth', require('./routes/user')) in index.js fle.

Let's work on user login now. In route/user.js paste this at the top

const jwt = require("jsonwebtoken")

and after user register logic paste the following

// Login user
router.post("/login", async (req, res) => {
  const { email, password } = req.body
  try {
    let user = await User.findOne({ email })
    if (!user) {
      return res.status(400).json({ error: "Invalid Credentials" })
    }
    const isMatched = await bcrypt.comapre(password, user.password)
    if (!isMatched) {
      return res.status(400).json({ error: "Invalid Credentials" })
    }
    const token = await jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
      expiresIn: "1h",
    })
    res.json({ token })
  } catch (err) {
    console.log(err)
  }
})

This time the route is /auth/login and again we are checking if email exists or not. If email does not exist then we will return an error then we are checking if password match and for that we used bcryptjs compare method which takes plain text password input by user and password stored in database. If password also matched then we will sign the token because when we will visit protected routes we have to send the token in headers. sign method takes user id, a secret key and expiry time (if you want token to expire after some time). Go to .env file and add

JWT_SECRET=provide-any-value-here

The login functionality is done here. If you run npm run server now and go to postman and make a post request to http://localhost:5000/auth/register providing name, email and password, you should see a message User created successfully and another post request to http://localhost:5000/auth/login providing email and password, upon successful login you will see token in response.

To be able to visit protected route, we need to verify the token to check whether token is valid or not and we will do that with the help of a middleware. At root level create a folder middleware and add a file auth.js inside it and paste the following

const jwt = require("jsonwebtoken")

exports.requireLogin = (req, res, next) => {
  try {
    if (req.headers.authorization) {
      const token = req.headers.authorization.split(" ")[1]
      // Verify token
      const decode = jwt.verify(token, process.env.JWT_SECRET)
      req.user = decode
      next()
    } else {
      return res.status(400).json({ error: "Unauthorized" })
    }
  } catch (err) {
    console.log("Something went wrong")
  }
}

At the top, we imported jsonwebtoken. After that we exported a function requireLogin which takes request, response and next as arguments. The way it works is like we will have to add Authorization in headers when we make request like Authorization: Bearer token. So we checked if authorization is present in headers, if its there then we will grab token from it and since it will be Bearer token so we split it by space and get the second value which will be index 1 in an array.

Now comes the verification process and for that we used verify method from jsonwebtoken which takes token and secret key. We attached the token to request and call the next function which will pass the control to the route we are trying to execute. So for example if we have a protected route "/" then if token verification is successful then the code inside of protected route function will execute otherwise the else block of auth middleware will run.

Let's try this. In index.js file at the top import requireLogin

const { requireLogin } = require("../middleware/auth")

and after the login route paste the following

router.get("/", requireLogin, async (req, res) => {
  console.log(req.user)
  try {
    const user = await User.findById(req.user._id).select("-password")
    res.json(user)
  } catch (err) {
    console.log(err)
  }
})

The path here is /auth and its a get request, as second argument we passed requireLogin function. In try block we find the user by id and req.user._id will be available to us because of requireLogin middleware because in middleware/auth.js file we attached the token which contains user id to req.user. Make a get request in postman at http://localhost:5000/auth without passing Authorization headers, you will see an error Unauthorized and when you pass the Authorization headers you will get the user details.

Share

Privacy Policy
icon
icon
icon
icon

Developed By Farhan Farooq