php login form php react mysql jwt authentication system

How To Create Login Form (PHP – ReactJS – MySQL – JWT authentication) With Rest API

Used before category names. Blog PHP React JS

Every dynamic system on the Internet needs to update its information by website administrators. For this, these managers enter the management section and update it. How these managers enter the system is called login. In this article, we are going to explain how to create a login form and system using PHP for the backend, ReactJS for the front end, MySQL for the database, and JWT for the authentication system.

In fact, we intend to create an authentication system in this article that you can use in your projects. To understand this tutorial, you must be familiar with ReactJS, PHP, MySQL, HTML, CSS, and Rest API.

What is an authentication system?

The authentication system is a system that can be used to limit the access of administrators at different levels and users to parts of the website. For example, only website administrators can access the management section using their usernames and password.

Website users can access parts of the website without authentication, but they must be logged in to access parts of the website. For example, they must be logged in to see the list of their previous orders.

In this tutorial, we will use PHP language, React technology, and MySQL database to create a login form and authentication system. We will also use the JWT method to identify logged-in users.

What is PHP?

PHP is a server-side scripting language that we will use to respond to user requests (client side). Our data is stored in the MySql database, which we will connect to this database using PHP and respond to the client’s requests.

What is react?

ReactJS is not a programming language but a technology created using the JavaScript scripting language. In this tutorial, we will use React for user-side design. This technology increases the user experience and even increases the coding speed.

React has made it simple for us how to send and receive data from the server and display it. This technology uses AJAX to send and receive data. If you are not familiar with AJAX, we have provided you with an article entitled How to use AJAX in WordPress, from which you can study the basic concepts of AJAX.

Before technologies like ReactJS, we had to use raw JavaScript to send and receive data as AJAX. It is true that React also uses JavaScript for this, but it has made it very simple for us.

What is MySQL?

MySQL is a database where we can store our data. This database is one of the most powerful and popular databases that is used as a database in many websites today. WordPress, which is built with about 30% of the websites on the Internet today, also uses MySql as a database. If you are interested in how to work with a database in WordPress, we have provided an article with this title for you.

What is JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

For beginning developers, JSON stands for JavaScript Object Notation and is a text-based format for transmitting data across web applications. It stores information in an easy-to-access manner, both for developers and computers.

A common way to use JWTs is as OAuth bearer tokens. In this example, an authorization server creates a JWT at the request of a client and signs it so that it cannot be altered by any other party. The client will then send this JWT with its request to a REST API. The REST API will verify that the JWT’s signature matches its payload and header to determine that the JWT is valid. When the REST API has verified the JWT, it can use the claims to either grant or deny the client’s request.

What Is Token?

A token is a string of data that represents something else, such as an identity. In the case of authentication, a non-JWT-based token is a string of characters that allows the receiver to validate the sender’s identity. The important distinction here is the lack of meaning within the characters themselves. 

MySQL database design for the login form and authentication system in PHP and React

First of all, we need to design the database so that we can store user information in it.

php login form php react mysql jwt authentication

In this database, the user’s profile and account status are stored. The AccountConfirm table will be used to confirm the user account after registration so that a code will be sent to the user’s email, which will be stored in the AccountConfirm table. If the entered code is the same as the code in the database, the status of the user account will change to active mode.

To create tables, enter PHPMyAdmin and create your database. In this example, we will use the database named login_system.

To create the User table, enter the database you created and enter the following code in the SQL field.

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci NOT NULL,
  `lastname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `status` int(11) NOT NULL,
  `created_date` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
)

Enter the following code for the AccountConfirm table.

CREATE TABLE IF NOT EXISTS `accountconfirm` (
  `user_id` int(11) NOT NULL,
  `code` varchar(5) NOT NULL,
  UNIQUE KEY `user_id` (`user_id`)
)

In the User table, the username and email fields are unique, so that each email or username in the entire table belongs to only one person. In the AccountConfirm table, the user_id field is the same.

How to install PHP

You can use WampServer or Xampp software to install PHP on your local host. Both of these software simulates the site server on the local host and you can use it to launch PHP websites. This software installs Apache and MySQL at the same time, so there is no need to install them separately.

How to install ReactJS

We also use NodeJS to launch and install React. You can download and install this software from the official NodeJS site.

After installing NodeJS, it’s time to launch the project itself. We use VS Code editor software for the project. VS Code is open-source software that you can download and install from its official website.

Creation of Login form and system project with react and PHP

The login form uses React as the front end and PHP as the back end, so it is necessary to create a project where both ReactJS and PHP can be implemented. Because React can be executed in any folder on your system by NodeJS, so the limitation is on PHP. Go to the path you gave when installing Wamp or Xampp. If you are using Wamp, go to the “www” folder and if you are using Xampp, go to the “htdocs” folder.

Open VS Code in this folder and enter the following code in the terminal section.

npx create-react-app hs-login-system

This command will start installing the React project. The term hs-login-system is the name of the project, you can change it to your desired name.

After installing React, open the hs-login-system folder using VS Code.

The React project contains a sample project that will run the sample designed page that we will modify later. Enter the following command in the Terminal to run the React project.

npm start

After running the project, you will see that NodeJS opens your browser and runs the project at the following address by default.

http://localhost:3000

Pages required to launch the PHP React login form project

In this project, “login”, “register”, “AccountConfirm”, “ResetPassword”, “logout”, and “profile” pages will be implemented. The Login page is used to implement the user login form, the register page is used to register the user, AccountConfirm is used to confirm the user account, ResetPassword is used to recover the password, Logout is used to exit the user account, and Profile is used to display user information.

The home page also refers to the main page on all websites, but because in this project we only intend to implement the login system, we will avoid implementing the home page professionally and we will only implement the communication between the pages.

The start of the login form and system by PHP and React project

After installing and setting up the React project, we need to create a structure for this project, which we will discuss further.

This project is divided into two parts: PHP, which will be the RestAPI, and React, which will be responsible for managing user-side pages.

All our Rest requests will be sent to the PHP API, which will be authenticated by JWT, and if the request is correct, an appropriate response will be given to it.

To create Rest API, create the following folders and files so that we can check their contents one by one. Create an api.php file in the root folder of the project. Then create the classes folder and create two files named database.php and jwt.php inside it.

Create a folder called view inside the src folder and create the following files in it.

  • confirm.js 
  • home.js
  • login.js
  • logout.js
  • profile.js
  • register.js
  • reset.js

Now the structure of your files and folders should be like the structure below.

php login system php react mysql jwt authentication

Now we will check each of the files and the codes are written in it.

jwt.php file

This file and the functions in it are responsible for working with JWT. Such as creating a token, validating it, and extracting data from the token.

Open the jwt.php file and put the following codes in it.

<?php
function generate_jwt($headers, $payload, $secret = 'secret')
{
    $headers_encoded = base64url_encode(json_encode($headers));

    $payload_encoded = base64url_encode(json_encode($payload));

    $signature = hash_hmac(
        'SHA256',
        "$headers_encoded.$payload_encoded",
        $secret,
        true
    );
    $signature_encoded = base64url_encode($signature);

    $jwt = "$headers_encoded.$payload_encoded.$signature_encoded";

    return $jwt;
}

function getSigniture($token){
    $_token=explode(".",$token);
    return $_token[2];
}

function getPayload($token)
{

    $_token=explode(".",$token);

    $payload=json_decode(base64UrlDecode($_token[1]));

    return $payload;
}

function base64UrlDecode(string $base64Url): string
{
    return base64_decode(strtr($base64Url, '-_', '+/'));
}

function is_jwt_valid($jwt, $secret = 'secret')
{
    // split the jwt
    $tokenParts = explode('.', $jwt);
    $header = base64_decode($tokenParts[0]);
    $payload = base64_decode($tokenParts[1]);
    $signature_provided = getSigniture($jwt);

    // check the expiration time - note this will cause an error if there is no 'exp' claim in the jwt
    // $expiration = json_decode($payload)->exp;
    // $is_token_expired = $expiration - time() < 0;

    // build a signature based on the header and payload using the secret
    $base64_url_header = base64url_encode($header);
    $base64_url_payload = base64url_encode($payload);
    $signature = hash_hmac(
        'SHA256',
        $base64_url_header . '.' . $base64_url_payload,
        $secret,
        true
    );
    $base64_url_signature = base64url_encode($signature);

    // verify it matches the signature provided in the jwt
    $is_signature_valid = $base64_url_signature === $signature_provided;

    if (/*$is_token_expired || */!$is_signature_valid) {
        return false;
    } else {
        return true;
    }
}

function base64url_encode($data)
{
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function get_authorization_header()
{
    $headers = null;

    if (isset($_SERVER['Authorization'])) {
        $headers = trim($_SERVER['Authorization']);
    } elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
        //Nginx or fast CGI
        $headers = trim($_SERVER['HTTP_AUTHORIZATION']);
    } elseif (function_exists('apache_request_headers')) {
        $requestHeaders = apache_request_headers();
        // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
        $requestHeaders = array_combine(
            array_map('ucwords', array_keys($requestHeaders)),
            array_values($requestHeaders)
        );
        //print_r($requestHeaders);
        if (isset($requestHeaders['Authorization'])) {
            $headers = trim($requestHeaders['Authorization']);
        }
    }

    return $headers;
}

function get_bearer_token()
{
    $headers = get_authorization_header();

    // HEADER: Get the access token from the header
    if (!empty($headers)) {
        if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
            return $matches[1];
        }
    }
    return null;
}

database.php file

This file is responsible for communicating with the database and CRUD operations in it. If you are familiar with three-layer programming, this file is the Data Layer.

Open the database.php file and save the following codes in it.

<?php
class Database
{
    private $server_name = 'localhost';
    private $database_username = 'root';
    private $database_password = '';
    private $database_name = 'login_system';
    private $connection = null;

    public function register($user)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'INSERT INTO user (`name`, `lastname`, `username`, `password`, `email`, `status`, `created_date`) VALUES (?,?,?,?,?,?,?)'
        );
        $sql->bind_param(
            'sssssis',
            $user['name'],
            $user['lastname'],
            $user['username'],
            $user['password'],
            $user['email'],
            $user['status'],
            $user['created_date']
        );
        if ($sql->execute()) {
            $id = $this->connection->insert_id;
            $sql->close();
            $this->connection->close();
            return $id;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function generateConfirmCode($user_id)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'INSERT INTO `accountconfirm`(`user_id`, `code`) VALUES(?,?) ON DUPLICATE KEY UPDATE    
            code=?'
        );
        $code = rand(11111, 99999);
        $sql->bind_param('iss', $user_id, $code, $code);
        if ($sql->execute()) {
            $sql->close();
            $this->connection->close();
            return $code;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function confirmCode($user_id, $code)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'SELECT * FROM `accountconfirm` WHERE user_id=? AND code=?'
        );
        $sql->bind_param('is', $user_id, $code);
        $sql->execute();
        $result = $sql->get_result();
        if ($result->num_rows > 0) {
            $sql->close();
            $this->connection->close();
            return true;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function activeUser($user_id)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'UPDATE `user` SET `status` = 1 WHERE id=?'
        );
        $sql->bind_param('i', $user_id);
        if ($sql->execute()) {
            $sql->close();
            $this->connection->close();
            return true;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function loginUser($username, $password)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'SELECT * FROM `user` WHERE username=? AND password=?'
        );
        $sql->bind_param('ss', $username, $password);
        $sql->execute();
        $result = $sql->get_result();
        if ($result->num_rows > 0) {
            $user = $result->fetch_assoc();
            $sql->close();
            $this->connection->close();
            return $user;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function getUserByUsernameOrEmail($username)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'SELECT DISTINCT * FROM `user` WHERE username=? OR email=?'
        );
        $sql->bind_param('ss', $username, $username);
        $sql->execute();
        $result = $sql->get_result();
        if ($result->num_rows > 0) {
            $user = $result->fetch_assoc();
            $sql->close();
            $this->connection->close();
            return $user;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }

    public function updateUser($user)
    {
        $this->connection = new mysqli(
            $this->server_name,
            $this->database_username,
            $this->database_password,
            $this->database_name
        );
        $this->connection->set_charset('utf8');
        $sql = $this->connection->prepare(
            'UPDATE `user` SET `name` = ?,`lastname`=?,`username`=?,`password`=?,`email`=? WHERE id=?'
        );
        $sql->bind_param(
            'sssssi',
            $user['name'],
            $user['lastname'],
            $user['username'],
            $user['password'],
            $user['email'],
            $user['id']
        );
        if ($sql->execute()) {
            $sql->close();
            $this->connection->close();
            return true;
        }
        $sql->close();
        $this->connection->close();
        return false;
    }
}

This file is connected to the login_system database on localhost and performs the following operations.

  • Register: This function receives the user’s profile and stores it in the database.
  • generateConfirmCode: All users must confirm their account after registration. This function creates a 5-digit confirmation code and stores it in the “accountconfirm” table.
  • confirmCode: This function receives the user id and the code entered by the user and retrieves its record, which returns true if there is a record (the entered code is correct) and false otherwise.
  • activeUser: the user account is changed from inactive to active after the user account is confirmed. This function does this by changing the status value in the user table from 0 to 1.
  • loginUser: This function receives the username and password and returns the desired user.
  • getUserByUsernameOrEmail: This function receives the username or email and returns the desired user.
  • updateUser: This function is responsible for updating the user profile.

api.php file

This file is responsible for receiving requests and responding to them from the user’s side. All data from the user side is sent to this file as AJAX, and this file, which is implemented as a RestAPI, gives the appropriate response to all requests.

Open the api.php file and copy the following codes in it.

<?php
include './classes/database.php';
include './classes/jwt.php';

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode('/', $uri);

$action = $uri[3];

$bearer_token = get_bearer_token();
$is_jwt_valid = isset($bearer_token) ? is_jwt_valid($bearer_token) : false;

$database = new Database();

if ($action === 'register') {
    $rest_json = file_get_contents('php://input');
    $_POST = json_decode($rest_json, true);
    $user = [
        'name' => $_POST['name'],
        'lastname' => $_POST['lastname'],
        'username' => $_POST['username'],
        'password' => md5($_POST['password']),
        'email' => $_POST['email'],
        'status' => 0,
        'created_date' => date('Y-m-d H:i:s'),
    ];

    if ($user_id = $database->register($user)) {
        $user['id'] = $user_id;
        if ($code = $database->generateConfirmCode($user_id)) {
            //send generated code by email to user
            $headers = ['alg' => 'HS256', 'typ' => 'JWT'];
            $payload = ['user' => $user];
            $jwt = generate_jwt($headers, $payload);
            return_json(['status' => $jwt]);
        }
    }
} elseif ($action === 'confirm') {
    if ($is_jwt_valid) {
        $rest_json = file_get_contents('php://input');
        $_POST = json_decode($rest_json, true);
        $user_id = getPayload($bearer_token)->user->id;

        if ($database->confirmCode($user_id, $_POST['code'])) {
            if ($database->activeUser($user_id)) {
                return_json(['status' => 1]);
            }
        }
    }
} elseif ($action === 'login') {
    $rest_json = file_get_contents('php://input');
    $_POST = json_decode($rest_json, true);

    if (
        $user = $database->loginUser(
            $_POST['username'],
            md5($_POST['password'])
        )
    ) {
        $headers = ['alg' => 'HS256', 'typ' => 'JWT'];
        $payload = ['user' => $user];
        $jwt = generate_jwt($headers, $payload);
        return_json(['status' => $jwt]);
    }
} elseif ($action === 'reset') {
    $rest_json = file_get_contents('php://input');
    $_POST = json_decode($rest_json, true);

    if ($user = $database->getUserByUsernameOrEmail($_POST['username'])) {
        $generated_password = uniqid(round(11111, 99999));
        $user['password'] = md5($generated_password);
        if ($database->updateUser($user)) {
            //send password ($generated_password value) to user by email
            return_json(['status' => 1]);
        }
    }
} elseif ($action === 'user') {
    if ($is_jwt_valid) {
        $username = getPayload($bearer_token)->user->username;
        if ($user = $database->getUserByUsernameOrEmail($username)) {
            return_json(['status' => $user]);
        }
    }
}
return_json(['status' => 0]);

function return_json($arr)
{
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: *');
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($arr);
    exit();
}

This file receives all user requests and validates them using JWT and extracts the required data from the database and sends them to the user in the form of JSON.

This file uses the database class and calls related operations, such as user registration, which is basically the registration of user information in the database. In three-layer programming, this layer is the Business Layer.

Actions available in this API are listed below.

  • Register: This action is responsible for receiving user information and registering them in the database. If the registration is successful, the user’s profile will be converted into a token using JWT and returned to the user.
  • Confirm: The task of this action is to receive the token and confirmation code from the user and confirm whether the code is valid or not. The user id is extracted from the token to determine if the user is valid or not.
  • Login: receives the username and password from the user and verifies whether they are correct or not.
  • Reset: Receives the username or email from the user and creates a new password for it and changes the password. The thing you should consider here is that since this project is a sample login form and systems, it is only written for how to connect PHP, React, and MYSQL with Rest API and JWT, and it is not a real project. Therefore, some parts must be changed in real projects, such as this part. In this code, anyone who knows a user’s email can change his password by resetting the account every time, which should be prevented in real projects.
  • User: This action returns the user profile.
  • return_json function: this function receives an array of data, converts them to JSON, and sends it to the user.

Note: In real projects, the data received from the user must be validated to prevent hacker attacks, but since this is not a real project, we have refrained from doing this in order not to complicate the codes and their readability.

Create the Home page

The Home page is responsible for displaying the connection between the pages. This page is managed by the home.js file inside the src folder. To create this connection, we need the “Link” component, and to use it, we need to add the react-router-dom package to the project. To do this, enter the following command in the terminal.

npm i react-router-dom

By using react-router-dom we can use Link to communicate between pages. Of course, this component adds a lot of features to the project, which we will mention below.

We assume that you are already familiar with ReactJS and you know concepts such as components, render, etc. If you are not familiar with React, we will explain the codes to you as much as possible.

Put the following codes in this file and save it.

import { Link } from 'react-router-dom'

export default function Home() {
  return (
    <div className="home">
      <h1>Honar Systems Login System (PHP + MySQL + React + JWT)</h1>
      <div className="panel">
        <Link to="/register">Register</Link>
        {localStorage.getItem('token') ? (
          <>
            <Link to="/profile">Profile</Link>
            <Link to="/logout">Log Out</Link>
          </>
        ) : (
          <Link to="/login">Login</Link>
        )}
      </div>
    </div>
  )
}

As we said, this React code uses Link to link to other pages. If the user is logged in, the value of the token received from the server will be stored in the token variable in local storage, and we use this variable to determine whether the user has already logged in or not. If this value exists, the profile and logout link will be displayed for the user under this user who is already logged in. Otherwise, the Login link will be displayed to log in to the system.

This token is the JWT value received from the server, which contains the user information such as name, surname, email, etc. This value is hashed but not encrypted and anyone with this string can see its content.

Note: From the point of view of security, this token should not fall into the hands of people and hackers because anyone can introduce themselves as the user by viewing its content and setting it on their browser.

Register page

Register page UI is implemented in the register.js file. This page sends the user’s information to the API server using AJAX technology, and the result of this request is also received by this page.

Open the file and put the following codes in it.

import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'

export default function Login() {
  const navigate = useNavigate()
  const [name, setName] = useState('')
  const [lastname, setLastName] = useState('')
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [email, setEmail] = useState('')

  const nameHandler = (event) => {
    setName(event.target.value)
  }

  const lastnameHandler = (event) => {
    setLastName(event.target.value)
  }

  const usernameHandler = (event) => {
    setUsername(event.target.value)
  }

  const passwordHandler = (event) => {
    setPassword(event.target.value)
  }

  const emailHandler = (event) => {
    setEmail(event.target.value)
  }

  async function registerRequest() {
    try {
      await fetch('http://localhost/hs-login-system/api/register', {
        method: 'POST',
        body: JSON.stringify({
          name: name,
          lastname: lastname,
          username: username,
          password: password,
          email: email,
        }),
      })
        .then((respose) => {
          if (respose.ok) {
            return respose.json()
          }
          throw new Error('error')
        })
        .then((data) => {
          if (data.status) {
            localStorage.setItem('token', data.status)
            navigate('/confirm')
          } else {
            //set error
          }
        })
    } catch (error) {
      console.log(error.message)
    }
  }

  const submitHandler = (event) => {
    event.preventDefault()
    registerRequest()
  }

  return (
    <form className="register-form" onSubmit={submitHandler}>
      <h2>Register</h2>
      <label>Name</label>
      <input type="text" value={name} onChange={nameHandler} />
      <label>Last Name</label>
      <input type="text" value={lastname} onChange={lastnameHandler} />
      <label>Username</label>
      <input type="text" value={username} onChange={usernameHandler} />
      <label>Password</label>
      <input type="password" value={password} onChange={passwordHandler} />
      <label>Email</label>
      <input type="text" value={email} onChange={emailHandler} />
      <button>Register</button>
      <Link to="/login">Login</Link>
    </form>
  )
}

As you can see, this page contains the registration form and the function of sending and receiving requests.

The user profile is stored in variables that use the useState hook. Then, when submitting the form, the registerRequest() function is called. This function sends the input specification to the Rest API using AJAX and retrieves the result from the server.

The API address that has been entered actually refers to the same api.php file address and the register action to which the data sent is sent to this address.

Note: In real projects, the data must be validated before sending so that the correct information is sent to the server. Because these codes are executed on the user’s side, it is not guaranteed to prevent hacker attacks. But the user may have entered the information incorrectly, in which case it will be corrected. Also, in case of an error in the operation, to prevent the code from getting complicated, error messages have been removed in this project, which should be added in real projects.

Confirm Account page

After registering on the Register page, you can send the confirmation code generated by the server to the user either by email or SMS, or any other way. On this page, the user enters the received code to confirm the user account, and if the code is correct, it will be redirected to the main page.

Open the confirm.js file and save the following code in it.

import { useState } from 'react'
import { useNavigate } from 'react-router-dom'

export default function AccountConfirm() {
  const navigate = useNavigate()
  const [code, setCode] = useState('')

  const codeHandler = (event) => {
    setCode(event.target.value)
  }

  async function confirmRequest() {
    try {
      await fetch('http://localhost/hs-login-system/api/confirm', {
        method: 'POST',
        body: JSON.stringify({
          code: code,
        }),
        headers: {
          Authorization: 'Bearer ' + localStorage.getItem('token'),
        },
      })
        .then((respose) => {
          if (respose.ok) {
            return respose.json()
          }
          throw new Error('error')
        })
        .then((data) => {
          if (data.status === 1) {
            navigate('/')
          } else {
            //set error
          }
        })
    } catch (error) {
      console.log(error.message)
    }
  }

  const submitHandler = (event) => {
    event.preventDefault()
    confirmRequest()
  }

  return (
    <form className="register-form" onSubmit={submitHandler}>
      <h2>Confirm Code</h2>
      <label>Code</label>
      <input type="text" value={code} onChange={codeHandler} />
      <button>Confirm</button>
    </form>
  )
}

Login page

Now let’s assume that the user already exists and that we want to enter the system by entering the username and password. In this case, we use the login form.

Open the login.js file and put the following codes in it.

import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'

export default function Login() {
  const navigate = useNavigate()
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')

  const usernameHandler = (event) => {
    setUsername(event.target.value)
  }

  const passwordHandler = (event) => {
    setPassword(event.target.value)
  }

  const submitHandler = (event) => {
    event.preventDefault()
    loginRequest()
  }

  async function loginRequest() {
    try {
      await fetch('http://localhost/hs-login-system/api/login', {
        method: 'POST',
        body: JSON.stringify({
          username: username,
          password: password,
        }),
      })
        .then((respose) => {
          if (respose.ok) {
            return respose.json()
          }
          throw new Error('error')
        })
        .then((data) => {
          if (data.status) {
            localStorage.setItem('token', data.status)
            navigate('/')
          } else {
            //set error
          }
        })
    } catch (error) {
      console.log(error.message)
    }
  }

  return (
    <form className="login-form" onSubmit={submitHandler}>
      <h2>Login</h2>
      <label>Username</label>
      <input type="text" value={username} onChange={usernameHandler} />
      <label>Password</label>
      <input type="password" value={password} onChange={passwordHandler} />
      <button>Login</button>
      <Link to="/register">Register</Link>
      <Link to="/reset">Reset Password</Link>
    </form>
  )
}

Reset page

This page, which is implemented in the reset.js file is responsible for retrieving the password in such a way that the user receives an email containing the new password after entering the username.

As mentioned above, the main drawback of this code is that it changes the password in the database directly, which should be avoided in real projects.

Open the file and put the following codes in it.

import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'

export default function ResetPassword() {
  const navigate = useNavigate()
  const [username, setUsername] = useState('')

  const usernameHandler = (event) => {
    setUsername(event.target.value)
  }

  async function resetRequest() {
    try {
      await fetch('http://localhost/hs-login-system/api/reset', {
        method: 'POST',
        body: JSON.stringify({
          username: username,
        }),
      })
        .then((respose) => {
          if (respose.ok) {
            return respose.json()
          }
          throw new Error('error')
        })
        .then((data) => {
          if (data.status) {
            navigate('/')
          } else {
            //set error
          }
        })
    } catch (error) {
      console.log(error.message)
    }
  }

  const submitHandler = (event) => {
    event.preventDefault()
    resetRequest()
  }

  return (
    <form className="register-form" onSubmit={submitHandler}>
      <h2>Reset Password</h2>
      <label>Username or Email</label>
      <input type="text" value={username} onChange={usernameHandler} />
      <button>Reset Password</button>
      <Link to="/login">Login</Link>
    </form>
  )
}

Profile page

The user’s profile is displayed on the profile page. These specifications are in two forms, one of which is the specifications received from the server and the other is the specifications stored inside the token in the user’s browser.

To read the content of the token, you need to install the package and run the following code in the terminal.

npm i jwt-decode

The profile.js file contains the following codes.

import { useCallback, useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import jwt from 'jwt-decode'

export default function Profile() {
  const [user, setUser] = useState()
  const userToken = jwt(localStorage.getItem('token'))

  const getUser = useCallback(async () => {
    try {
      await fetch('http://localhost/hs-login-system/api/user', {
        headers: {
          Authorization: 'Bearer ' + localStorage.getItem('token'),
        },
      })
        .then((respose) => {
          if (respose.ok) {
            return respose.json()
          }
          throw new Error('error')
        })
        .then((data) => {
          setUser(data.status)
        })
    } catch (error) {
      console.log(error.message)
    }
  }, [])

  useEffect(() => {
    getUser()
  }, [getUser])

  return (
    <div className="profile">
      <h2>Profile</h2>
      {user && (
        <>
          <label>Name: {user.name}</label>
          <label>Last Name: {user.lastname}</label>
          <label>Username: {user.username}</label>
          <label>Email: {user.email}</label>
        </>
      )}
      <h2>User from token</h2>
      {userToken && (
        <>
          <label>Name: {userToken.user.name}</label>
          <label>Last Name: {userToken.user.lastname}</label>
          <label>Username: {userToken.user.username}</label>
          <label>Email: {userToken.user.email}</label>
        </>
      )}
      <Link to="/">Home</Link>
    </div>
  )
}

On this page, the information that is read from the token is for you to learn how to read the content of the token on the user side.

Logout page

This page is implemented in the logout.js file and is responsible for clearing the token. The point in this section is that we have considered a page for logout for simplicity and readability of the code, but you can easily do this on other pages.

import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'

export default function Logout() {
    const navigate = useNavigate()
  useEffect(() => {
    localStorage.removeItem('token')
    navigate('/')
  },[true])

  return (
    <></>
  )
}

Communication between React pages

So far, the pages, codes, and classes required for the project have been created. But in order to start, you need to change the default React code in some parts.

Go to the index.js page and change the render part code as below.

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
)

In the import section, enter the following module.

import { BrowserRouter } from 'react-router-dom'

As you know, React is known for being a single page. It means that all the pages are displayed on one page and there is no need to refresh the page to go to another page. But React does not support this possibility by default and we add this possibility by using Route.

To become a single page, all project codes must be placed inside the BrowserRouter component.

Pages are defined in the app.js file. Open the app.js file and enter the following codes.

import { Route, Routes } from 'react-router-dom'
import AccountConfirm from './view/confirm'
import Home from './view/home'
import Login from './view/login'
import Logout from './view/logout'
import Profile from './view/profile'
import Register from './view/register'
import ResetPassword from './view/reset'

function App() {
  return (
    <div className="App">
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/home" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/register" element={<Register />} />
        <Route path="/confirm" element={<AccountConfirm />} />
        <Route path="/reset" element={<ResetPassword />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/logout" element={<Logout />} />
      </Routes>
    </div>
  )
}
export default App

As you can see, the address of the above pages is defined using Routes and Route components in this file. For example, if we enter the address “/home”, the Home component will be called from the home.js file and the user will see the Home page.

Styling pages

React uses JavaScript, HTML, and CSS to create UI pages, so we can use CSS or even SCSS codes to style pages.

Open the index.css file and enter the following style codes.

a {
  text-decoration: none;
}

input {
  outline: none;
}

.home h1{
  text-align: center;
}

.home .panel {
  width: 600px;
  display: flex;
  justify-content: center;
  margin: 30px auto;
}

.home .panel a {
  width: 100%;
  text-align: center;
  display: inline-block;
  padding: 30px;
  box-shadow: #333 0 0 10px;
  border-radius: 15px;
  -webkit-border-radius: 15px;
  color: #333;
}

.home .panel a:hover{
  color: #fff;
  background: #333;
}

.login-form,
.register-form,
.profile {
  display: flex;
  flex-wrap: wrap;
  width: 400px;
  margin: 30px auto;
  padding: 30px 60px;
  box-shadow: #333 0 0 10px;
  border-radius: 15px;
  -webkit-border-radius: 15px;
}

.login-form label,
.register-form label,
.profile label {
  display: block;
  width: 100%;
  padding: 5px;
}

.login-form input,
.register-form input {
  display: block;
  width: 100%;
  padding: 10px;
  border: 1px solid #333;
  background: #fff;
}

.login-form button,
.register-form button {
  display: inline-block;
  margin-top: 15px;
  padding: 10px 20px;
  border: 1px solid #333;
  background: #333;
  color: #fff;
  cursor: pointer;
}

.login-form button:hover,
.register-form button:hover {
  background: #fff;
  color: #333;
}

.login-form a,
.register-form a,
.profile a {
  display: block;
  width: 100%;
  margin-top: 30px;
  color: #ec8313;
  font-weight: 700;
}

Run the login form and System project (PHP, React, MySQL, and JWT authentication)

The project is ready to run in this section, just enter the following command in the VS Code terminal.

npm start

The project will run on localhost and port 3000.

You can download the code of this project from Git Hub. To start the project you downloaded from Git Hub, extract the downloaded file and change the name of the extracted folder to hs-login-system. Move the folder to www in WampServer and htdocs in Xampp.

Open the project using VS Code and run the following command in the terminal to install the required packages.

npm i

After installing the packages, run the project using the command below.

npm start

Important note

In real projects in React, most of these operations are implemented using Context so that they can be used everywhere in the project, but in this project, we used this method to keep the code simple.

Security issues for the PHP React login form and system

There are some security issues in this example project. Do NOT have any data from the client in the server-side API, sanitize and validate all of them. For example, don’t trust data in PHP API that you receive from the login form (the username and password)

In this project to prevent complications of the database queries, we used just the username or id from the token received from the client side. In this case, if the hacker knows the real user username, he can hack that user account. In the real project, it is recommended to retrieve data by username and password of the user.