Authentication with react, express, mongoose and jwt - part 1

In this post we will discuss authentication and how to perform it in react apps. This is part 1 of the post and will contain basic terminologies and frontend of our authentication app in react. In the second post we will discuss the backend of authentication.

What is authentication

Authentication is a way of verifying the details of the user through the data present in database and make sure that the user is able to access only the data that the user has permission to view.

Why is authentication required?

Authentication makes sure that one user cannot access data of the other and only the users who have been already given an account or signed up can get access to the web application.

Then the next question which comes to mind is how do we authenticate the user. Of course it can just be done by matching username and password with the ones in database and a success response must be sent if they are matching. Though this sounds simple, there is one catch to it. What should we send back as response so that it will be easy for us to verify each time a new request comes in the backend, it is sent from this particular user? To solve this problem we must generate something unique so that our backend can recognize that this unique key or token belongs to a certain user and can then respond by giving the requested data to the user. The best way to achieve this is by using json web tokens which we can generate for user when he logs in and then whenever the user requests some data we can verify the user through his token.

What is JWT?

Json Web tokens or jwt in short is a way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWT mainly focuses on signed tokens, since these tokens can be verified. You can read more about Json Web tokens at the official documentation

How does JWT work for authentication?

When the user signs ups or logins, a jwt token is created with some expiration time and is sent to the user as response and this token will be used by the user for every request and using this token we can easily verify which user is requesting the information and can provide the information required. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.

Frontend for authentication

Let us make a simple interface for both login and signup in react.

Login

import React from "react";
import "./tailwind.output.css";

import { useState } from "react";
import { useNavigate } from "react-router-dom";

export default function App() {
  const [userData, setUserData] = useState({
    email: "",
    password: ""
  });
  const navigate = useNavigate();
  const submit = async (e) => {
    e.preventDefault();
    const response = await axios.post(
      "https://your-server-url/auth/login",
      {
        email: userData["email"],
        password: userData["password"]
      }
    );
    if (response.payload?.success) {
      localStorage.setItem('auth', JSON.stringify({ token: action.payload.token}))
      navigate('/')
    } else {
      console.log(response.payload);
    }
  };

  function onChangeHandler(e) {
    setUserData({
      ...userData,
      [e.target.id]: e.target.value
    });
  }

  return (
    <div>
      <div className="h-screen bg-gray-400 bg-center">
        <div className="container max-w-sm mx-auto my-auto px-2 pt-20">
          <form
            className="bg-white px-6 py-6 rounded shadow-md text-black w-full pt-5 mt-10"
            onSubmit={submit}
          >
            <h1 className="mb-5 text-3xl text-center pb-1">Login</h1>
            <h1 className="mb-5 text-xl text-left ml-2">Username</h1>
            <input
              id="email"
              type="Email"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              value={userData.email}
              required
              placeholder="Enter Email"
              autoComplete="off"
              onChange={onChangeHandler}
            />
            <h1 className="mb-5 text-xl text-left ml-2">Password</h1>
            <input
              id="password"
              type="password"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              required
              placeholder="Enter Password"
              onChange={onChangeHandler}
              value={userData.password}
            />
            <button
              className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded"
              type="submit"
            >
              Login
            </button>
            <h1
              className="mt-4 rounded p-2"
              style={{ backgroundColor: "#A7F3D0" }}
            >
              Not registered yet?{" "}
              <span className="cursor-pointer" style={{ color: "#065F46" }}>
                Signup
              </span>
            </h1>
          </form>
        </div>
      </div>
    </div>
  );
}

The login page will be like this,

image.png

Signup page

import React from "react";
import "./tailwind.output.css";

import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";

export default function App() {
  const [userData, setUserData] = useState({
    name: "",
    tag: "",
    email: "",
    password: "",
    "confirm password": ""
  });
  const navigate = useNavigate();
  const [confirmpasswordValid, updateConfirmPasswordValid] = useState(true);

  const submit = async (e) => {
    e.preventDefault();
    updateConfirmPasswordValid(
      userData["confirm password"] === userData["password"]
    );
    if (!confirmpasswordValid) {
      const response = await axios.post(
        "https://your-server-url/auth/signup",
        {
          name: userData["name"],
          email: userData["email"],
          password: userData["password"]
        }
      );
      if (response.payload?.success) {
         localStorage.setItem('auth', JSON.stringify({ token: action.payload.token}))
        navigate("/");
      } else {
        console.log(response.error);
      }
    }
  };

  function onChangeHandler(e) {
    setUserData({
      ...userData,
      [e.target.id]: JSON.parse(JSON.stringify(e.target.value))
    });
  }

  return (
    <div className="bg-gray-400">
      <div className="h-screen">
        <div className="container max-w-sm mx-auto my-auto px-2 pt-4">
          <form
            className="bg-white px-6 py-6 rounded shadow-md text-black w-full pt-5 mt-10"
            onSubmit={submit}
          >
            <h1 className="mb-5 text-2xl text-center pb-1">Signup</h1>
            <h1 className="mb-5 text-xl text-left ml-2">Email</h1>
            <input
              id="email"
              type="email"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              value={userData.email}
              required
              placeholder="Enter Email"
              autoComplete="off"
              onChange={onChangeHandler}
            />
            <h1 className="mb-5 text-xl text-left ml-2">name</h1>
            <input
              id="name"
              type="text"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              value={userData.name}
              required
              placeholder="Enter your name"
              autoComplete="off"
              onChange={onChangeHandler}
            />
            <h1 className="mb-5 text-xl text-left ml-2">Password</h1>
            <input
              id="password"
              type="password"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              value={userData.password}
              required
              placeholder="Enter Password"
              onChange={onChangeHandler}
            />
            <h1 className="mb-5 text-xl text-left ml-2">Confirm Password</h1>
            <input
              id="confirm password"
              type="password"
              className="block border border-grey-light w-full p-2 rounded mb-4 focus:outline-none focus:ring"
              value={userData["confirm password"]}
              required
              placeholder="Confirm Password"
              onChange={onChangeHandler}
            />
            <button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">
              Signup
            </button>
          </form>
        </div>
      </div>
    </div>
  );
}

Signup screen will be like this,

image.png

I have used tailwind css in both the pages. On submit the post request is made to the backend which takes the required arguments and if the signup/login is successful the token present in the response is saved in local storage of the user and the user is navigated to the home page. You can change this to any page you like.

We can also display errors if response is coming as error or the password don't match.

The onChangeHandler function updates the value of the particular field to the value entered by the user in that field. This information entered by the user is stored in userData object which is then used for sending request to the server to authenticate the user through login or create a new user through signup.

If you are using this template change the server URL to your server URL. The server setup is shared in the next blog.

This concludes this blog, i'll continue with the backend part of authentication in my next blog. Thanks for reading. I hope you enjoyed and it was useful for you.

If you enjoyed and learned from this article, I will like to connect with you. Connect with me on LinkedIn to be part of my journey.