Authentication with MERN Stack

Rivaan Ranawat
8 min readNov 19, 2021

--

Hello everyone, in this post, we are going to allow users to signup / login with email and password using the MERN Stack (short for MongoDB, Express.js, React.js and Node.js)

Key Features

  • Usage of JWT (JSON Web Token) and BcryptJS to securely store passwords
  • Persisting user login state with LocalStorage
  • State Management with useContext
  • Clean File Management
  • Client Side UI

Getting Started

For this tutorial, make sure you’ve created a React Project using the command npx create-react-app projectName. I will be using react-bootstrap and bootstrap package for Signup / Login Page UI which is completely optional for you to follow. You also want to install react-router-dom in your client side. Now migrate to your server folder and install jsonwebtoken, cors, express, bcryptjs and axios packages from npm. To make life easier, I’d also suggest installing nodemon as dev dependency and in the package.json file, add 2 lines in the scripts block mentioned below.

“start”: “node ./index.js”,

“dev”: “nodemon ./index.js”

Server:

First we will import express, mongoose, cors and after that instantiate express and store it in variable named app.

Now, we will create a User Model with mongoose in the folder named models named user.js which has the properties of email, password and username.

Here, setting required to true makes that property compulsory for you to add everytime User model is called. Setting unique to true makes sure no other user has the same username or email.

Now create a new folder named routes and here create a file named user.js.

Again, import all the necessary packages along with jwt, bcryptjs packages and the User Model.

Now, we need to create a Router based on whatever post/get requests we are going to create which we will export afterwards and use in the index.js file.

Code:

Explanation: Here we are creating 4 requests:

  1. a post request to path “/signup” where we get email, confirmPassword, password and username from the request body(we will attach request body from client side) and then run some validations like checking if all of them exist, password is atleast 6 or not and if confirm password is equal to password. Then, we check if there is an existing user with the same email or not, if there is, we send an error back but if there isn’t we make use of bcryptjs package to secure our password using the hash function and passing in password(which is the word, we want to secure) and then save the user with the hashed password in MongoDB.
  2. a post request to path “/login” where we get email and password from the request body and similar to signup path do validation but instead of checking if the user doesn't exist,we check if the user exists and if it does, we compare the password entered by user we receive from request body to the password present in the MongoDB and store it in the variable named isMatch using bcryptjs compare function (we cannot directly compare the two password because we have one password as the hashed password and other as the one user enters). Then, we need to create a token so that we can persist data in the frontend and thus we use jwt’s sign function.
  3. a post request to “/tokenIsValid” to check if the token we have received from the request header is valid or not (“x-auth-token” is the header name we are going to use in client side)
  4. a get request to “/” where it shows up user’s username and token.

Note in the get request, we have auth as the middleware, let’s create that!

Auth Middleware

Middleware in short is that code which runs even before the callback function code runs thus it is a way of validation/authentication for us in this case. Create a folder named middleware wherein we create a file named auth.js and copy the below code:

Here, we are getting x-auth-token, as mentioned before, from request header and then we are just verifying if that token exists and if it exists, if it is a verified token created from jwt or not. Then, we just save it to the user object and then very importantly run the next function which means that the code after it in the callback function runs, if we forget to put that, the next chunk of code won’t run!

Connection

We also need to run app.use command multiple times, first with parameter as express.json() that converts all the requests in json format, then with cors, then with userRouter we just created.

app.use(express.json());

app.use(cors());

app.use(userRouter);

Now, let’s connect to our MongoDB using this command:

mongoose.connect(

“mongodb://localhost:127.0.0.1:27017/redditClone”,

{ useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true },

() => {

console.log(“mongoose connected”);

}

);

Now, we can listen to our port on 3001 and log “connected” to the console to check if the connection is proper!

If all works fine till here and you receive no syntax errors, you can run this code and check on Postman or Thunder Client(VS Code Extension) to check if it runs well! If it does, let’s migrate to our client side and work there!

Login / Signup UI

I have provided you with the code for the UI of Login and Signup screens. I am not going to explain the code in detail but make sure to leave a comment if you have any doubts, I’ll clear them off! Note that I have used bootstrap and react-bootstrap both, so make sure to import bootstrap in the index.js file of client side using the import line given below(in ES6 format):

import “bootstrap/dist/css/bootstrap.min.css”

Login UI Code:

Signup UI Code:

After copying this code, you might have received an error which basically means that handleSubmit function (in both the files) isn’t defined so we are going to do just that but before that,make sure you have set the routes properly in your App.js file. If you’re not familiar with react-router-dom, make sure to read the documentation which is pretty clear and should leave you with no doubts!

Creating UserContext

I know, I said we need to create handleSubmit function but before that, a very important step is to create user context for great state management in your website which will make your website faster and your code easier to read and even use if you’re working in a company!

We are going to use the useContext hook provided by React itself, you can use Redux or Recoil as well, there is no problem as long as you’re familiar with it. If you’re not using Context API, you can skip to the next section. If not copy the below code and paste it in a folder named context with file named UserContext.js

import { createContext } from ‘react’;

export default createContext(null);

Here we are just creating a context which has null value and we are going to set it to a value that makes sense in the next section!

Handling User Form Interaction

Head on to your Signup.js file and create an asynchronous function (since we are going to use axios to send API requests) and accept e as a parameter which we are going to get from the form by default. After that, we need to prevent browser’s default behaviour to reload the website after the form has been submitted so we will use e.preventDefault(); in the starting of the function. Set isLoading hook to true and then create a try and catch block.

Inside the try block, we will create a variable called newUser which will contain the values from variables email, password, confirmPassword and username. Now, import axios and then use the command:

await axios.post(“http://localhost:3001/signup", newUser);

Using this, we are sending a post request to the above mentioned url (Remember we had created a post request url in our server folder) where we saved the user to our MongoDB.

Next, we need to again send a post request like this:

const loginRes = await axios.post(“http://localhost:3001/login", {

email,

password,

});

We need to store the data in loginRes since we need to store the token and user data with hashed password(which we are doing in the server folder) to our userData hook from UserContext file like this:

setUserData({

token: loginRes.data.token,

user: loginRes.data.user,

});

After that, we just to need to store the data in localStorage of the browser which can be done using:

localStorage.setItem(“auth-token”, loginRes.data.token);

You can set isLoading hook to false and redirect the user to the home screen using history.push(“/”)

In the catch block, we will get err as a parameter and we can set isLoading hook to false and use this command to display the error properly:

err.response.data.msg && setError(err.response.data.msg);

Here is the code for Signup Handle Function:

We are going to write similar code in Login.js file but just changing the url where we are going to send 1 post request. Here is the code:

Here we have sent only 1 post request and not 2 unlike Signup.js, reason being we don’t have to create a user again as the user is logging in which means user has already been created!

Setting and Passing User Data to Children

Head on to your App.js file, where we first need to create a userData hook which consists of token and user properties which are undefined. Then we check if the user is loggen is by getting auth-token field from localStorage and if the token is null, that means it has no value, ten we set auth token to a certain value. Then we check if the token we have recieved from local storage is valid by sending post request to the path and adding header of “x-auth-token” and store it in the variable named tokenRes. Then check if the tokenRes has any data, if there is, then we will set the user data to whatever data we get by sending a get request to the url with path “/” (returned username and token)

In the UI Part, we return a Switch component provided by react-router-dom and add our paths and components to it which is wrapped by UserContext Provider. This is for our state management where we pass in the data of userData to our children so that it is easily accessible without the use of calling useEffect in every component.

Code:

Conclusion

So this is it for this tutorial, if you have any errors like ENOENT, check your folder structure properly and then import on the top of the files. This is my folder structure in the client side:

Folder Structure (Client Side)
Folder Structure (Server Side)

Source Code For Reference: https://github.com/rivaanRanawat/socia/

Check Me Out Here

Instagram: https://www.instagram.com/optimalcoding/

Youtube: https://www.youtube.com/channel/UC-1kzHtwBY8n0TY5NhYxNaw

Portfolio Website: https://rivaanranawat.netlify.app/

--

--

Rivaan Ranawat

Hello There! I am Rivaan Ranawat, 19 year old coding enthusiast.