Register and Login with JWT Authentication (React JS + Express + MySQL + Node JS)

Register and Login with JWT Authentication (React JS + Express + MySQL + Node JS)

In this tutorial you will learn how to create login and registration using JWT (JSON Web Token) with node js, express, mysql on backend and react js on frontend.

Not only that,

I will also share with you how to create refresh token and save refresh token into httpOnly cookie so that our application will be safe from XSS (Cross-site Scripting) and CSRF (Cross-site Request Forgery) attacks.

Let's start.

Step #1. Backend

#1.1. Install Dependency

Create a folder on your computer, here I name it "jwt-auth".

You are free to make it anywhere, either in C, D, or on the Desktop.

Then open the "jwt-auth" folder using the code editor, here I use Visual Studio Code.

I also suggest you to use Visual Studio Code.

You can download Visual Studio Code at the following link, then install it on your computer:

https://code.visualstudio.com/

After the "jwt-auth" folder is opened using Visual Studio Code, create a sub folder named "backend" in the "jwt-auth" folder.

Next, open a terminal in Visual Studio Code on the menu bar terminal => new terminal.

After that, type the following command in the terminal to create a “package.json” file:

npm init -y

Next, install express, mysql2, sequelize, jsonwebtoken, bcrypt, cookie-parser, dotenv and cors by typing the following command in the terminal:

npm install express mysql2 sequelize jsonwebtoken bcrypt cookie-parser dotenv cors

Next, install nodemon globally by typing the following command in the terminal:

npm install -g nodemon

Next, add the following code to the “package.json” file:

"type": "module",

So that the “package.json” file looks like the following:

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "mysql2": "^2.3.2",
    "sequelize": "^6.8.0"
  }
}

This is so that we can use ES6 Module Syntax to export and import modules.

 

#1.2. Create a Database

To be able to use MySQL, you need to install XAMPP, WAMP, MAMP, or similar software.

In this tutorial, I use XAMPP.

Then create a new database in MySQL, you can use tools such as SQLyog, PHPMyAdmin or similar tools.

Here I create a database with the name "auth_db".

If you create a database with the same name, that's even better.

To create a database in MySQL, it can be done by executing the following query:

CREATE DATABASE auth_db;

The SQL command above will create a database with the name "auth_db".

 

#1.3. Application Structure

To make the application more structured neatly, we will apply the MVC pattern (Model-View-Controllers).

Create “config”, “controllers”, “middleware”, “models”, and “routes” folders in the “backend” folder.

Then create the file "Database.js" in the folder "config", create the file "Users.js" and "RefreshToken.js" in the folder "controllers", create the file "VerifyToken.js" in the folder "middleware", create "UserModel.js" file in the "models" folder, create the "index.js" file in the "routes" folder, and create the "index.js" file in the "backend" folder.

Look at the following picture for more details:

app structure

 

#1.4. Connect to Database

Open the "Database.js" file in the "config" folder, then type the following code:

import { Sequelize } from "sequelize";

const db = new Sequelize('auth_db', 'root', '', {
    host: "localhost",
    dialect: "mysql"
});

export default db;

 

#1.5. Models

Open the "UserModel.js" model file located in the "models" folder, then type the following code:

import { Sequelize } from "sequelize";
import db from "../config/Database.js";

const { DataTypes } = Sequelize;

const Users = db.define('users',{
    name:{
        type: DataTypes.STRING
    },
    email:{
        type: DataTypes.STRING
    },
    password:{
        type: DataTypes.STRING
    },
    refresh_token:{
        type: DataTypes.TEXT
    }
},{
    freezeTableName:true
});

(async () => {
    await db.sync();
})();

export default Users;

 

#1.6. Controllers

Open the controller file "Users.js" which is in the "controllers" folder, then type the following code:

import Users from "../models/UserModel.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";

export const getUsers = async(req, res) => {
    try {
        const users = await Users.findAll({
            attributes:['id','name','email']
        });
        res.json(users);
    } catch (error) {
        console.log(error);
    }
}

export const Register = async(req, res) => {
    const { name, email, password, confPassword } = req.body;
    if(password !== confPassword) return res.status(400).json({msg: "Password and Confirm Password do not match"});
    const salt = await bcrypt.genSalt();
    const hashPassword = await bcrypt.hash(password, salt);
    try {
        await Users.create({
            name: name,
            email: email,
            password: hashPassword
        });
        res.json({msg: "Registration Successful"});
    } catch (error) {
        console.log(error);
    }
}

export const Login = async(req, res) => {
    try {
        const user = await Users.findAll({
            where:{
                email: req.body.email
            }
        });
        const match = await bcrypt.compare(req.body.password, user[0].password);
        if(!match) return res.status(400).json({msg: "Wrong Password"});
        const userId = user[0].id;
        const name = user[0].name;
        const email = user[0].email;
        const accessToken = jwt.sign({userId, name, email}, process.env.ACCESS_TOKEN_SECRET,{
            expiresIn: '15s'
        });
        const refreshToken = jwt.sign({userId, name, email}, process.env.REFRESH_TOKEN_SECRET,{
            expiresIn: '1d'
        });
        await Users.update({refresh_token: refreshToken},{
            where:{
                id: userId
            }
        });
        res.cookie('refreshToken', refreshToken,{
            httpOnly: true,
            maxAge: 24 * 60 * 60 * 1000
        });
        res.json({ accessToken });
    } catch (error) {
        res.status(404).json({msg:"Email not found"});
    }
}

export const Logout = async(req, res) => {
    const refreshToken = req.cookies.refreshToken;
    if(!refreshToken) return res.sendStatus(204);
    const user = await Users.findAll({
        where:{
            refresh_token: refreshToken
        }
    });
    if(!user[0]) return res.sendStatus(204);
    const userId = user[0].id;
    await Users.update({refresh_token: null},{
        where:{
            id: userId
        }
    });
    res.clearCookie('refreshToken');
    return res.sendStatus(200);
}

After that, open the controller file "RefreshToken.js" which is in the "controllers" folder, then type the following code:

import Users from "../models/UserModel.js";
import jwt from "jsonwebtoken";

export const refreshToken = async(req, res) => {
    try {
        const refreshToken = req.cookies.refreshToken;
        if(!refreshToken) return res.sendStatus(401);
        const user = await Users.findAll({
            where:{
                refresh_token: refreshToken
            }
        });
        if(!user[0]) return res.sendStatus(403);
        jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => {
            if(err) return res.sendStatus(403);
            const userId = user[0].id;
            const name = user[0].name;
            const email = user[0].email;
            const accessToken = jwt.sign({userId, name, email}, process.env.ACCESS_TOKEN_SECRET,{
                expiresIn: '15s'
            });
            res.json({ accessToken });
        });
    } catch (error) {
        console.log(error);
    }
}

 

#1.7. Middleware

Open the "VerifyToken.js" file located in the "middleware" folder, then type the following code:

import jwt from "jsonwebtoken";

export const verifyToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    if(token == null) return res.sendStatus(401);
    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => {
        if(err) return res.sendStatus(403);
        req.email = decoded.email;
        next();
    })
}

 

#1.8. Routes

Open the "index.js" file in the "routes" folder, then type the following code:

import express from "express";
import { getUsers, Register, Login, Logout } from "../controllers/Users.js";
import { verifyToken } from "../middleware/VerifyToken.js";
import { refreshToken } from "../controllers/RefreshToken.js";

const router = express.Router();

router.get('/users', verifyToken, getUsers);
router.post('/users', Register);
router.post('/login', Login);
router.get('/token', refreshToken);
router.delete('/logout', Logout);

export default router;

 

#1.9. Entry Point

Open the “index.js” file located in the “backend” folder, then type the following code:

import express from "express";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";
import cors from "cors";
import db from "./config/Database.js";
import router from "./routes/index.js";
dotenv.config();
const app = express();

app.use(cors({ credentials:true, origin:'http://localhost:3000' }));
app.use(cookieParser());
app.use(express.json());
app.use(router);

app.listen(5000, ()=> console.log('Server running at port 5000'));

 

#1.10. .env

Create a file named “.env” in the “backend” folder, then type the following code:

ACCESS_TOKEN_SECRET = jsfgfjguwrg8783wgbjs849h2fu3cnsvh8wyr8fhwfvi2g225
REFRESH_TOKEN_SECRET = 825y8i3hnfjmsbv7gwajbl7fobqrjfvbs7gbfj2q3bgh8f42

You are free to set values in ACCESS_TOKEN_SECRET and REFRESH_TOKEN_SECRET above.

To make sure the application runs properly, run the application by typing the following command in the terminal:

nodemon index

If it goes well, it will look like the following image:

app running

Up here you have successfully created a "backend".

 

Step #2. Frontend

#2.1. Create React App

For the front-end, I will use React JS.

If you are not familiar with React JS, I suggest you to learn the “React JS Tutorial For Beginners” first.

There are many ways to create a React js project. However, the easiest way is "create react app".

Open a new terminal and create a new React project by typing the following command in the terminal:

npx create-react-app frontend

And make sure you are in the “jwt-auth” folder, like the following picture:

create-react-app

If the installation is complete, there will be a "frontend" folder in the "jwt-auth" folder.

So that in the "jwt-auth" folder there are two folders, namely: "backend" and "frontend".

Next, go to the “frontend” folder by typing the following command in the terminal:

cd frontend

After that, install react-router-dom, axios, jwt-decode, and bulma by typing the following command in the terminal:

npm install react-router-dom axios jwt-decode bulma

After the installation is complete, and to make sure everything goes well, type the following command to run the project:

npm start

If it goes well, it will look like the following image:

npm-start

 

#2.2. Components

Create a folder called “components” inside the “frontend/src” folder.

Then, create component files “Dashboard.js”, “Login.js”, “Navbar.js”, and “Register.js” in the “frontend/src/components” folder.

Look at the following picture for more details:

components

 

#2.3. Registration

Open the "Register.js" file located in the "frontend/src" folder, then type the following code:

import React, { useState } from 'react'
import axios from "axios";
import { useHistory } from "react-router-dom";

const Register = () => {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confPassword, setConfPassword] = useState('');
    const [msg, setMsg] = useState('');
    const history = useHistory();

    const Register = async (e) => {
        e.preventDefault();
        try {
            await axios.post('http://localhost:5000/users', {
                name: name,
                email: email,
                password: password,
                confPassword: confPassword
            });
            history.push("/");
        } catch (error) {
            if (error.response) {
                setMsg(error.response.data.msg);
            }
        }
    }

    return (
        <section className="hero has-background-grey-light is-fullheight is-fullwidth">
            <div className="hero-body">
                <div className="container">
                    <div className="columns is-centered">
                        <div className="column is-4-desktop">
                            <form onSubmit={Register} className="box">
                                <p className="has-text-centered">{msg}</p>
                                <div className="field mt-5">
                                    <label className="label">Name</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Name"
                                            value={name} onChange={(e) => setName(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Email</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={password} onChange={(e) => setPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Confirm Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={confPassword} onChange={(e) => setConfPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <button className="button is-success is-fullwidth">Register</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    )
}

export default Register

 

#2.4. Login

Open the "Login.js" file located in the "frontend/src" folder, then type the following code:

import React, { useState } from 'react'
import axios from 'axios';
import { useHistory } from 'react-router-dom';

const Login = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [msg, setMsg] = useState('');
    const history = useHistory();

    const Auth = async (e) => {
        e.preventDefault();
        try {
            await axios.post('http://localhost:5000/login', {
                email: email,
                password: password
            });
            history.push("/dashboard");
        } catch (error) {
            if (error.response) {
                setMsg(error.response.data.msg);
            }
        }
    }

    return (
        <section className="hero has-background-grey-light is-fullheight is-fullwidth">
            <div className="hero-body">
                <div className="container">
                    <div className="columns is-centered">
                        <div className="column is-4-desktop">
                            <form onSubmit={Auth} className="box">
                                <p className="has-text-centered">{msg}</p>
                                <div className="field mt-5">
                                    <label className="label">Email or Username</label>
                                    <div className="controls">
                                        <input type="text" className="input" placeholder="Username" value={email} onChange={(e) => setEmail(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <label className="label">Password</label>
                                    <div className="controls">
                                        <input type="password" className="input" placeholder="******" value={password} onChange={(e) => setPassword(e.target.value)} />
                                    </div>
                                </div>
                                <div className="field mt-5">
                                    <button className="button is-success is-fullwidth">Login</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    )
}

export default Login

 

#2.5. Dashboard

Open the “Dashboard.js” file located in the “frontend/src” folder, then type the following code:

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import jwt_decode from "jwt-decode";
import { useHistory } from 'react-router-dom';

const Dashboard = () => {
    const [name, setName] = useState('');
    const [token, setToken] = useState('');
    const [expire, setExpire] = useState('');
    const [users, setUsers] = useState([]);
    const history = useHistory();

    useEffect(() => {
        refreshToken();
        getUsers();
    }, []);

    const refreshToken = async () => {
        try {
            const response = await axios.get('http://localhost:5000/token');
            setToken(response.data.accessToken);
            const decoded = jwt_decode(response.data.accessToken);
            setName(decoded.name);
            setExpire(decoded.exp);
        } catch (error) {
            if (error.response) {
                history.push("/");
            }
        }
    }

    const axiosJWT = axios.create();

    axiosJWT.interceptors.request.use(async (config) => {
        const currentDate = new Date();
        if (expire * 1000 < currentDate.getTime()) {
            const response = await axios.get('http://localhost:5000/token');
            config.headers.Authorization = `Bearer ${response.data.accessToken}`;
            setToken(response.data.accessToken);
            const decoded = jwt_decode(response.data.accessToken);
            setName(decoded.name);
            setExpire(decoded.exp);
        }
        return config;
    }, (error) => {
        return Promise.reject(error);
    });

    const getUsers = async () => {
        const response = await axiosJWT.get('http://localhost:5000/users', {
            headers: {
                Authorization: `Bearer ${token}`
            }
        });
        setUsers(response.data);
    }

    return (
        <div className="container mt-5">
            <h1>Welcome Back: {name}</h1>
            <table className="table is-striped is-fullwidth">
                <thead>
                    <tr>
                        <th>No</th>
                        <th>Name</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    {users.map((user, index) => (
                        <tr key={user.id}>
                            <td>{index + 1}</td>
                            <td>{user.name}</td>
                            <td>{user.email}</td>
                        </tr>
                    ))}

                </tbody>
            </table>
        </div>
    )
}

export default Dashboard

 

#2.6. Navbar

Open the "Navbar.js" file located in the "frontend/src" folder, then type the following code:

import React from 'react'
import axios from 'axios';
import { useHistory } from 'react-router-dom';

const Navbar = () => {
    const history = useHistory();

    const Logout = async () => {
        try {
            await axios.delete('http://localhost:5000/logout');
            history.push("/");
        } catch (error) {
            console.log(error);
        }
    }

    return (
        <nav className="navbar is-light" role="navigation" aria-label="main navigation">
            <div className="container">
                <div className="navbar-brand">
                    <a className="navbar-item" href="https://bulma.io">
                        <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" alt="logo" />
                    </a>

                    <a href="/" role="button" className="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                        <span aria-hidden="true"></span>
                    </a>
                </div>

                <div id="navbarBasicExample" className="navbar-menu">
                    <div className="navbar-start">
                        <a href="/" className="navbar-item">
                            Home
                        </a>
                    </div>

                    <div className="navbar-end">
                        <div className="navbar-item">
                            <div className="buttons">
                                <button onClick={Logout} className="button is-light">
                                    Log Out
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </nav>
    )
}

export default Navbar

 

#2.7. App.js

Open the "App.js" file in the "frontend" folder, then change it to the following:

import { BrowserRouter, Route, Switch } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import Login from "./components/Login";
import Navbar from "./components/Navbar";
import Register from "./components/Register";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Login/>
        </Route>
        <Route path="/register">
          <Register/>
        </Route>
        <Route path="/dashboard">
          <Navbar/>
          <Dashboard/>
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

export default App;

 

#2.8. index.js

Open the "index.js" file in the "frontend" folder, then change it to the following:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import "bulma/css/bulma.css";
import axios from "axios";

axios.defaults.withCredentials = true;

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

 

Step #3. Testing

Go back to the browser and visit the following URL:

http://localhost:3000/register

If it goes well, it will look like the following image:

register

Enter your name, email, password, and confirm password, then click the Register button.

If it goes well, it will be directed to the login form as follows:

login

Enter your username and password, then click the Login button.

If it goes well, it will be directed to the dashboard page like the following:

dashboard

To logout, click the “Logout” button, it will be directed to the login form.

 

Conclusion:

The discussion this time is how to build Login and Registration using JWT (JSON Web Token) with node js, express, react js, and MySQL.

Not only that, you have also learned how to create a refresh token so that when the access token expires, the user does not need to log in again.

So what are you waiting for, Let's Coding!

Download Source Code (Back-End) Download Source Code (Front-End)

Comments (0)

Leave a Comment