How to implement Authentication on a website

How to implement Authentication on a website

Prerequisite:

To follow along with this article, you are gonna need to have the following below but if you don't, feel free still enjoy.

  • Basics of React.JS
  • Knowledge of Node.js and Express
  • Mongo Db

Technologies included in this tutorial:

  • Axios
  • JsonWebToken
  • Bcrypt

Alright, so you have got the requirements down but probably you don't know what authentication is or probably you have heard about it but you don't know how it works. First thing first let's get a sense of what is authentication

So what is Authentication?

Authentication refers to the process of identifying an individual, usually based on a username, password, and some type of additional verification. Authentication confirms that an individual is who they claim to be, preventing unauthorized access to a program, system, network, or device, but does not affect the individual's access rights.

Overview of the implementation

On the front end part of this application, you will have a login and sign-up form. For the sign-up part, a user will enter data and it will be sent to the backend where the password and other credentials will be received, however, the password will be hashed, a token will be created to identify the user, and sent back to the frontend along with the other data. As for the login part, the user will now have to enter the credential they had entered on the signup page, this data will also be sent to the backend where it will be checked if the user exists and if the user exists and it will check if the password is correct and if all this condition is met, then the user will now be logged in.

File Structure

image.png

Demo of the project

View video

Source code can be found on my Github @:(github.com/KyngCoder/auth)

Frontend Section

This part will simply just contain a sign and login form and will be styled using tailwindcss.

Let's start by creating the signup component

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

const SignUp = () => {
  const [name, setName] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [password, setPassword] = useState('');
  const [show, setShow] = useState(false);

  const navigate = useNavigate()

  const handleSubmit = async (event) => {
    event.preventDefault();
    if (!name || !password || !confirmPassword) {
      alert("Please fill out all fields");
      return;
    }
    if (password !== confirmPassword) {
      alert("Passwords do not match");
      return;
    }

    try {
      const { data } = await axios.post("http://localhost:5000/user/signup", {
        name,
        password,
      });
           localStorage.setItem("userInfo", JSON.stringify(data));
      alert("Successfully signed up!");

      navigate('/')
    } catch (error) {

      alert("Error signing up");
    }
  };



  return (
    <div className="flex justify-center items-center
    h-screen">
      <form className="w-full max-w-lg">
        <div className="flex flex-wrap -mx-3 ">
          <div className="w-full px-3 mb-6 md:mb-0">
            <label
              className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
              htmlFor="grid-first-name"
            >
              Username
            </label>
            <input
              className="appearance-none block w-full bg-gray-200 text-gray-700 border border-red-500 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"
              id="grid-first-name"
              type="text"
              placeholder="Ricky"
              value={name}
              onChange={(e) => setName(e.target.value)}
            />
            <p className="text-red-500 text-xs italic">
              Please fill out this field.
            </p>
          </div>
          </div>

        <div className="flex flex-wrap -mx-3 mb-6">
          <div className="w-full px-3">
            <label
              className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
              htmlFor="grid-password"
            >
              Password
            </label>
            <input
              className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
              id="grid-password"
              type={show ? "text" : "password"}
              placeholder="******************"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
           <div className = "flex justify-between">
           <p className="text-gray-600 text-xs italic">
              Make it as long and as crazy as you'd like
            </p>
            <button className="hover:text-green-500" type="button" onClick={() => setShow(!show)}>
              {show ? "Hide" : "Show"}
            </button>
           </div>
          </div>
        </div>
        <div className="flex flex-wrap -mx-3 mb-6">
          <div className="w-full px-3">
            <label
              className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
              htmlFor="grid-confirmPassword"
            >
              Confirm Password
            </label>
            <input
              className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
              id="grid-confirmPassword"
              type={show ? "text" : "password"}
              placeholder="******************"
              value={confirmPassword}
              onChange={(e) => setConfirmPassword(e.target.value)}
            />
            <p className="text-gray-600 text-xs italic">
              Repeat password
            </p>
          </div>
        </div>

        <div className="w-full px-3 mb-6 md:mb-0  rounded-sm ">
            <button className="bg-blue-500 w-full hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" onClick={handleSubmit}>Sign Up</button>
            </div>
      </form>
    </div>
  );
};

export default SignUp;

This is a simple form containing a username field, a password, a confirm password field, and a button to submit the data. Whenever you click the button:

  • Prevents the page from refreshing
  • Checks to see that all fields were filled out
  • Then it checks to see if the password and confirm password are the same
  • When all these conditions are met the data is sent to the backend, where some magic takes place, well not magic but the password is hashed and a token is created for the user and sent back to the frontend where it is stored in local storage
  • If all of this is successful, the user will be granted access to the homepage

Login Component

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

const Login = () => {
  const [name, setName] = useState('');
  const [password, setPassword] = useState();
  const [show, setShow] = useState(false);

  const navigate = useNavigate()
  const handleSubmit = async (event) => {
    event.preventDefault();
    if (!name || !password) {
      alert("Please fill out all fields");
      return;
    }

    console.log(name, password);
    try {
      const { data } = await axios.post("http://localhost:5000/user/login", {
        name,
        password,
      });
      console.log(data);
      alert("Successfully Logged In!");
      localStorage.setItem("userInfo", JSON.stringify(data));

      navigate('/')
    } catch (error) {
      console.log(error);
      alert("Error logging in");
    }
  };

  return (
    <div className="flex justify-center h-screen items-center">
      <form className="w-full max-w-lg">
        <div className="flex flex-wrap -mx-3 ">
          <div className="w-full px-3 mb-6 md:mb-0">
            <label
              className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
              for="grid-first-name"
            >
              Username
            </label>
            <input
              className="appearance-none block w-full bg-gray-200 text-gray-700 border border-red-500 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"
              id="grid-first-name"
              type="text"
              placeholder="***********"
              value={name}
              onChange={(e) => setName(e.target.value)}
            />

          </div>
          </div>

        <div className="flex flex-wrap -mx-3 mb-6">
          <div className="w-full px-3">
            <label
              className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
              for="grid-password"
            >
              Password
            </label>
            <input
              className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
              id="grid-password"
              type={show ? "text" : "password"}
              placeholder="******************"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
            <div className = "flex justify-between">
           <p className="text-gray-600 text-xs italic">
             Please enter your password.
            </p>
            <button className="hover:text-green-500" type="button" onClick={() => setShow(!show)}>
            {show ? "Hide" : "Show"}
            </button>
           </div>
          </div>
        </div>


        <div className="w-full md:w-1/3 px-3 mb-6 md:mb-0 bg-blue-500 rounded-sm flex justify-center">
            <button className="p-2 flex text-white" onClick={handleSubmit}>Login</button>
            </div>
      </form>
    </div>
  );
};

export default Login;

This is just a form to check if the user exists and if they entered the correct credential

Homepage Component

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

const HomePage = () => {
  const [token, setToken] = React.useState(false);

  const navigate = useNavigate()

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem("userInfo"));

    if (user) setToken(true)
  }, [navigate]);

  const signOut = () => {
    localStorage.removeItem("userInfo");
    setToken(false);
  }


  return (
    <div className="flex justify-center items-center
    h-screen">
      {token ? (
        <div>
          <h1>Welcome to the Home Page</h1>
          <button className = "mx-4 bg-blue-500 text-white p-2 rounded-sm hover:bg-red-500" onClick={signOut}>Sign Out</button>
        </div>
      ) : (
        <div className="flex bg-blue-400 shadow-inner p-4 rounded-sm border-blue-400">
          <button className="mx-4 bg-blue-500 text-white p-2 rounded-sm hover:bg-green-600" onClick={()=>navigate('/login')}>Login</button>
          <button className="mx-4 bg-blue-500 text-white p-2 rounded-sm hover:bg-red-500" onClick={()=> navigate('/signup')}>SignUp</button>
        </div>
      )}
    </div>
  );
};

export default HomePage;

As it regards this home page, what this component does is that it checks to see if the user has the right to see the homepage.

  • If they have access, they will see a text saying "welcome to the homepage" and a button to log out

  • If not, they will be prompt to either signup if they don't already have an account or login if they do have an account

App.js file

import SignUp from "./SignUp";
import HomePage from "./HomePage";
import Login from "./Login";
import {BrowserRouter as Router, Route, Routes} from "react-router-dom";

function App() {
  return (
    <div>
    <Router>
      <Routes>
        <Route path="/" element={<HomePage />} exact />
        <Route path="/signup" element={<SignUp />} exact/>
        <Route path="/login" element={<Login />} exact/>
      </Routes>
    </Router>
    </div>
  );
}

export default App;

This file showcases the routing setup of each component

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

That's it for the frontend

Lets move to the frontend

.env file

You will be needing 3 different variable

  • Mongo DB URL

  • Port variable

  • Secret variable (just type anything)

Connecting to the database

const mongoose = require('mongoose');

const connectDb = async () => {
    try{
        const connection = await mongoose.connect(process.env.MONGO_URL)
        console.log(`MongoDB connected: ${connection.connection.host}`)
    }catch(error){
        console.log(error)
    }
}

module.exports = connectDb;

Main file

const express = require('express')
const dotenv = require('dotenv')
const cors = require('cors')
const connectDb = require('./connectDb')

const userRoutes = require('./userRoute.js')


const app = express()
dotenv.config()
connectDb()

app.use(cors())
app.use(express.json())


app.use('/user',userRoutes)

const PORT = process.env.PORT || 5000

app.listen(PORT, ()=> console.log(`server started on port : ${PORT}`))

Creating a model for user

const mongoose = require('mongoose')

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

})



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

This model expects a name field and a password field

Creating user Routes

const express = require("express");
const { signUp, loginUser } = require("./userController");

const router = express.Router();

router.post("/signup", signUp);
router.post("/login", loginUser);

module.exports = router;

This contains two routes, the signup, and login routes but they are depending on a function that is to be provided by the user controller

User Controller

Sign up method


const signUp = async (req, res) => {
  const { name, password } = req.body;

  try {
    const existingUser = await User.findOne({ name });
    if (existingUser)
      return res.status(400).json({ message: "User already exists." });


    const hashedPassword = await bcrypt.hash(password, 10);

    const result = await User.create({
      password: hashedPassword,
      name:name
    });
    console.log(result);

    const token = jwt.sign(
      { name: result.name, id: result._id },
       process.env.SECRET,
        { expiresIn: "1h",});

    res.status(200).json({ result, token });
  } catch (error) {
    res.status(500).json({ message: 'something went wrong' });
  }
};

This is receiving the name and password from the frontend

It first checks to see if the username is already taken If it ain't taken, it hashed the password Creates a user in the database with the hashed password It then creates a token and sends it back to the frontend along with the user info created and if for some reason something went wrong along this line, an error will be thrown

Login method

const loginUser = async (req, res) => {
    const { name, password } = req.body;

    try {
      const existingUser = await User.find({ name });

      if (!existingUser) {
        return res.status(404).json({ message: "User do not exist" });
      }

      const isPasswordCorrect = await bcrypt.compare(
        password,
        existingUser[0].password
      );

      if (!isPasswordCorrect) {
        console.log("wrong password");
        return res.status(400).json({ message: "Invalid credentials" });
      }

      const token = jwt.sign(
        { name: existingUser[0].name, id: existingUser[0].id },
        "test",
        { expiresIn: "1h" }
      );

      res.status(200).json({ result: existingUser, token });
    } catch (error) {
        console.log(error)
      return res.status(500).json({ message: "something went wrong" });
    }
  };

This method receives the username and password. It first checks to see if the user exists in the database, if the user doesn't exist; well we don't have anything else to do. If the user exists, we checked to see that the correct password was entered. If these conditions are met we just create a new token and send it back to the front.

That's basically it. Please feel free to share your thoughts in the comments, ask your queries if you have any and if I made a mistake, please feel free to correct me

Did you find this article valuable?

Support Ricardo Merchant by becoming a sponsor. Any amount is appreciated!