Регистрация и авторизация в php с JSON Web Token

Сегодня мы реализуем форму регистрации, входа, выхода и обновления учётной записи пользователя, используя JSON Web Token.

  1. Что такое JWT?
  2. Файловая структура
  3. Настройка базы данных
  4. Создание API для регистрации пользователей
  5. Создание API для входа пользователей
  6. Создание API для валидации JWT
  7. Создание API для учетных записей пользователей
  8. Создание интерфейса для регистрации пользователей
  9. Создание интерфейса для входа пользователей
  10. Создание интерфейса домашней страницы
  11. Создание интерфейса для страницы аккаунта

1. Что такое JWT

JWT (JSON Web Token) — это формат упаковки данных, который позволяет обеспечить безопасную передачу информации между клиентом и сервером в JSON-формате.

Токены будут создаваться на сервере, подписываться секретным ключом с последующей передачей клиенту для подтверждения своей личности.

Информация будет надёжно защищена, т.к. она будет иметь цифровую подпись.

Пример: сервер генерирует токен «пользователь вошёл как администратор» и предоставляет его клиенту. Затем клиент использует этот токен для доказательства того, он он вошёл «как администратор».

2. Файловая структура

  • authentication-jwt/
    • api/
      • Config/
        • Core.php
        • Database.php
      • libs/
        • php-jwt/
      • Objects/
        • User.php
      • create_user.php
      • login.php
      • update_user.php
      • validate_token.php
    • custom.css
    • index.html

3. Настройка базы данных

Создадим базу данных authentication_jwt, а в ней таблицу users.

Таблица следующего вида:

PHP JWT - Таблица базы данных users
-- phpMyAdmin SQL Dump

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";

-- 
-- База данных: `api_db`
-- 

-- --------------------------------------------------------

-- 
-- Структура таблицы `users`
-- 

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `firstname` varchar(256) NOT NULL,
  `lastname` varchar(256) NOT NULL,
  `email` varchar(256) NOT NULL,
  `password` varchar(2048) NOT NULL,
  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 
-- Индексы сохранённых таблиц
-- 

-- 
-- Индексы таблицы `users`
-- 
ALTER TABLE `users`
  ADD PRIMARY KEY (`id`);

-- 
-- AUTO_INCREMENT для сохранённых таблиц
-- 

-- 
-- AUTO_INCREMENT для таблицы `users`
-- 
ALTER TABLE `users`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;COMMIT;

Создадим папку для проекта authentication-jwt.

Я буду использовать OpenServer, поэтому папку проекта я создам в следующей директории:

D:\OSPanel\domains\authentication-jwt\

После того как у вас структура подготовлена (смотрите п.2), откроем файл Database.php в папке api/Config/ — файл для соединения с базой данных.

Добавим в него следующий код:

<?php

// Используем для подключения к базе данных MySQL
class Database
{
    // Учётные данные базы данных
    private $host = "localhost";
    private $db_name = "authentication_jwt";
    private $username = "root";
    private $password = "root";
    public $conn;

    // Получаем соединение с базой данных
    public function getConnection()
    {
        $this->conn = null;

        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
        } catch (PDOException $exception) {
            echo "Ошибка соединения с БД: " . $exception->getMessage();
        }

        return $this->conn;
    }
}

4. Создание API для регистрации пользователей

Нам нужно установить заголовки, чтобы файл для создания пользователей принимал только данные JSON

В папке api в файл сreate_user.php добавим следующий код.

<?php

// Заголовки
header("Access-Control-Allow-Origin: http://authentication-jwt/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// Подключение к БД
// Файлы, необходимые для подключения к базе данных
include_once "./Config/Database.php";
include_once "../Objects/User.php";

// Получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// Создание объекта "User"
$user = new User($db);
 
// Отправляемые данные будут здесь

Информация о пользователе будет отправлена через HTML-форму и JavaScript код. Мы увидим это позже. Нам необходимо назначить отправляемые данные в свойствах объекта, такие как имя, фамилия и т.п.

Замените комментарий // Отправляемые данные будут здесь в файле сreate_user.php следующим кодом:

// Получаем данные
$data = json_decode(file_get_contents("php://input"));
 
// Устанавливаем значения
$user->firstname = $data->firstname;
$user->lastname = $data->lastname;
$user->email = $data->email;
$user->password = $data->password;

// Поверка на существование e-mail в БД
// $email_exists = $user->emailExists();
 
// Здесь будет метод create()

Мы будем использовать метод create(), который сообщит нам о том, был создан пользователь или нет.

Замените комментарий // Здесь будет метод create() в файле create_user.php следующим кодом:

// Создание пользователя
if (
    !empty($user->firstname) &&
    !empty($user->email) &&
    // $email_exists == 0 &&
    !empty($user->password) &&
    $user->create()
) {
    // Устанавливаем код ответа
    http_response_code(200);
 
    // Покажем сообщение о том, что пользователь был создан
    echo json_encode(array("message" => "Пользователь был создан"));
}
 
// Сообщение, если не удаётся создать пользователя
else {
 
    // Устанавливаем код ответа
    http_response_code(400);
 
    // Покажем сообщение о том, что создать пользователя не удалось
    echo json_encode(array("message" => "Невозможно создать пользователя"));
}

Создание класса User

Предыдущий файл не будет работать без класса User.

Откройте api/Objects/User.php и внесите следующий код:

<?php

class User
{
    // Подключение к БД таблице "users"
    private $conn;
    private $table_name = "users";

    // Свойства
    public $id;
    public $firstname;
    public $lastname;
    public $email;
    public $password;

    // Конструктор класса User
    public function __construct($db)
    {
        $this->conn = $db;
    }

    // Метод для создания нового пользователя
    function create()
    {

        // Запрос для добавления нового пользователя в БД
        $query = "INSERT INTO " . $this->table_name . "
                SET
                    firstname = :firstname,
                    lastname = :lastname,
                    email = :email,
                    password = :password";

        // Подготовка запроса
        $stmt = $this->conn->prepare($query);

        // Инъекция
        $this->firstname = htmlspecialchars(strip_tags($this->firstname));
        $this->lastname = htmlspecialchars(strip_tags($this->lastname));
        $this->email = htmlspecialchars(strip_tags($this->email));
        $this->password = htmlspecialchars(strip_tags($this->password));

        // Привязываем значения
        $stmt->bindParam(":firstname", $this->firstname);
        $stmt->bindParam(":lastname", $this->lastname);
        $stmt->bindParam(":email", $this->email);

        // Для защиты пароля
        // Хешируем пароль перед сохранением в базу данных
        $password_hash = password_hash($this->password, PASSWORD_BCRYPT);
        $stmt->bindParam(":password", $password_hash);

        // Выполняем запрос
        // Если выполнение успешно, то информация о пользователе будет сохранена в базе данных
        if ($stmt->execute()) {
            return true;
        }

        return false;
    }

    // Здесь будет метод emailExists()
}

Тестирование API

Можно использовать POSTMAN для тестирования нашего API. Скачать его можно здесь.

Сначала мы проверим успешность создания пользователя. Запустим POSTMAN.

Нажмите Collections, далее +, метод запроса POST и введите следующий URL запрос:

http://authentication-jwt/api/create_user.php

Нажмите Body, затем raw и вставьте следующее значение JSON.

{
  "firstname" : "Vasiliy",
  "lastname" : "Ivanov",
  "email" : "vasya@coder.com",
  "password" : "888"
}

Нажмите кнопку Send. Если сделали верно, должны увидеть следующее:

POSTMAN - тест создания пользователя

Чтобы проверить ошибку создания пользователя, просто удалите значение пароля и нажмите кнопку Send.

POSTMAN - невозможно создать пользователя

5. Создание API для входа пользователей

В приведенном ниже коде установим заголовки, чтобы файл знал откуда должен поступить запрос и какой тип данных принимается.

Откроем файл api/login.php и поместим в него следующий код.

<?php

// Заголовки
header("Access-Control-Allow-Origin: http://authentication-jwt/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
 
// Здесь будет соединение с БД

Мы сравним электронную почту пользователя и пароль из базы данных, поэтому нам нужно подключение к БД.

Нам также нужно создать экземпляр пользовательской таблицы, это позволит нам проверить, существует ли электронная почта, и прочитать хешированный пароль.

Замените комментарий // Здесь будет соединение с БД в файле login.php следующим кодом:

// Файлы необходимые для соединения с БД
include_once "./Config/Database.php";
include_once "../Objects/User.php";
 
// Получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
 
// Создание объекта "User"
$user = new User($db);
 
// Получаем данные
$data = json_decode(file_get_contents("php://input"));
 
// Устанавливаем значения
$user->email = $data->email;
$email_exists = $user->emailExists();
 
// Файлы для JWT будут здесь

Проверка на существование e-mail

Добавим метод emailExists() в наш класс User.

Этот метод вернёт истину (true), если отправленный e-mail существует, иначе вернёт ложь (false).

Замените комментарий // Здесь будет метод emailExists() в файле /api/Objects/User.php следующим кодом.

// Проверка, существует ли электронная почта в нашей базе данных
function emailExists() {
 
    // Запрос, чтобы проверить, существует ли электронная почта
    $query = "SELECT id, firstname, lastname, password
            FROM " . $this->table_name . "
            WHERE email = ?
            LIMIT 0,1";
 
    // Подготовка запроса
    $stmt = $this->conn->prepare($query);
 
    // Инъекция
    $this->email=htmlspecialchars(strip_tags($this->email));
 
    // Привязываем значение e-mail
    $stmt->bindParam(1, $this->email);
 
    // Выполняем запрос
    $stmt->execute();
 
    // Получаем количество строк
    $num = $stmt->rowCount();
 
    // Если электронная почта существует,
    // Присвоим значения свойствам объекта для легкого доступа и использования для php сессий
    if ($num > 0) {
 
        // Получаем значения
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
        // Присвоим значения свойствам объекта
        $this->id = $row["id"];
        $this->firstname = $row["firstname"];
        $this->lastname = $row["lastname"];
        $this->password = $row["password"];
 
        // Вернём "true", потому что в базе данных существует электронная почта
        return true;
    }
 
    // Вернём "false", если адрес электронной почты не существует в базе данных
    return false;
}
 
// Здесь будет метод update()

Создание JWT (JSON web token)

Заменим комментарий // Файлы для JWT будут здесь в файле api/login.php на следующий код:

// Подключение файлов JWT
include_once "config/core.php";
include_once "libs/php-jwt/BeforeValidException.php";
include_once "libs/php-jwt/ExpiredException.php";
include_once "libs/php-jwt/SignatureInvalidException.php";
include_once "libs/php-jwt/JWT.php";
use \Firebase\JWT\JWT;
 
// Существует ли электронная почта и соответствует ли пароль тому, что находится в базе данных
if ($email_exists && password_verify($data->password, $user->password)) {
 
    $token = array(
       "iss" => $iss,
       "aud" => $aud,
       "iat" => $iat,
       "nbf" => $nbf,
       "data" => array(
           "id" => $user->id,
           "firstname" => $user->firstname,
           "lastname" => $user->lastname,
           "email" => $user->email
       )
    );
 
    // Код ответа
    http_response_code(200);
 
    // Создание jwt
    $jwt = JWT::encode($token, $key, 'HS256');
    echo json_encode(
        array(
            "message" => "Успешный вход в систему",
            "jwt" => $jwt
        )
    );
}
 
// Если электронная почта не существует или пароль не совпадает,
// Сообщим пользователю, что он не может войти в систему
else {
 
  // Код ответа
  http_response_code(401);

  // Скажем пользователю что войти не удалось
  echo json_encode(array("message" => "Ошибка входа"));
}
?>

Создание файла конфигурации (ядра)

Файл login.php не будет работать без файла core.php. Этот файл содержит общие настройки / переменные нашего приложения.

У нас есть переменные, используемые нашей библиотекой JWT для кодирования и декодирования токена. Значение $key должно быть вашим собственным и уникальным секретным ключом.

  • iss — адрес или имя удостоверяющего центра;
  • aud — имя клиента для которого токен выпущен;
  • iat — время, когда был выпущен JWT;
  • nbf — время, начиная с которого может быть использован (не раньше, чем).

Вы также можете использовать exp — идентифицирует время истечения срока действия токена.

Откроем api/config/core.php и добавим следующий код.

<?php

// Показ сообщений об ошибках
error_reporting(E_ALL);
 
// Установим часовой пояс по умолчанию
date_default_timezone_set("Europe/Moscow");
 
// Переменные, используемые для JWT
$key = "your_secret_key";
$iss = "http://any-site.org";
$aud = "http://any-site.com";
$iat = 1356999524;
$nbf = 1357000000;

Скачайте библиотеку PHP-JWT c GitHub’а.

Скопируйте содержимое библиотеки в api/libs/.

Тест входа в систему

Введём следующий URL запрос:

http://authentication-jwt/api/login.php

В Body вставьте следующее значение JSON.

{
  "email" : "vasya@coder.com",
  "password" : "888"
}

Нам нужно сохранить сгенерированный JWT чтобы потом его использовать для проверки.

POSTMAN - вход пользователя

Для проверки на неудачный вход в систему измените значение пароля на 222 (это неверный пароль).

POSTMAN - ошибка входа пользователя

6. Создание API для валидации JWT

Приступим к наполнению файла api/validate_token.php.

Установим правильные заголовки. Этот файл вернет вывод в формате JSON и примет запросы от указанного URL.

<?php

// Заголовки
header("Access-Control-Allow-Origin: http://localhost/rest-api-authentication-example/");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
 
// Требуется для декодирования JWT
include_once "config/core.php";
include_once "libs/php-jwt/BeforeValidException.php";
include_once "libs/php-jwt/ExpiredException.php";
include_once "libs/php-jwt/SignatureInvalidException.php";
include_once "libs/php-jwt/JWT.php";
include_once "libs/php-jwt/Key.php";
use \Firebase\JWT\JWT;
use \Firebase\JWT\Key; 
 
// Получаем значение веб-токена JSON
$data = json_decode(file_get_contents("php://input"));

// Получаем JWT
$jwt = isset($data->jwt) ? $data->jwt : "";

// Если JWT не пуст
if ($jwt) {
 
    // Если декодирование выполнено успешно, показать данные пользователя
    try {

        // Декодирование jwt
        $decoded = JWT::decode($jwt, new Key($key, 'HS256'));
 
        // Код ответа
        http_response_code(200);
 
        // Покажем детали
        echo json_encode(array(
            "message" => "Доступ разрешен",
            "data" => $decoded->data
        ));
    }
 
    // Если декодирование не удалось, это означает, что JWT является недействительным
    catch (Exception $e) {
    
        // Код ответа
        http_response_code(401);
    
        // Сообщим пользователю что ему отказано в доступе и покажем сообщение об ошибке
        echo json_encode(array(
            "message" => "Вам доступ закрыт",
            "error" => $e->getMessage()
        ));
    }
}
 
// Покажем сообщение об ошибке, если JWT пуст
else {
 
    // Код ответа
    http_response_code(401);
 
    // Сообщим пользователю что доступ запрещен
    echo json_encode(array("message" => "Доступ запрещён"));
}

Тест на успешный доступ

Введём следующий URL:

http://authentication-jwt/api/validate_token.php

Введите JSON токен, который вы получили ранее. Приведенный ниже токен JSON отличается от вашего.

{
  "jwt" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzNTcwMDAwMDAsImRhdGEiOnsiaWQiOiIyIiwiZmlyc3RuYW1lIjoiVmFzaWxpeSIsImxhc3RuYW1lIjoiSXZhbm92IiwiZW1haWwiOiJ2YXN5YUBjb2Rlci5jb20ifX0.W4ghyd6D4jRAnOeiT4-wTjdjaGh9EYmJDlssrai6A80"
}

Так должно выглядеть в POSTMAN:

Валидация JWT токена

Чтобы проверить наличие неудачного доступа, просто добавьте слово «EDITED» в свой JWT. Это сделает JWT неправильным и приведет к отказу в доступе. Это должно выглядеть так.

POSTMAN - ошибка входа пользователя

7. Создание API для учетных записей пользователей

Откроем api/update_user.php и вставим следующий код.

<?php

// Заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// Требуется для кодирования веб-токена JSON
include_once "config/core.php";
include_once "libs/php-jwt/BeforeValidException.php";
include_once "libs/php-jwt/ExpiredException.php";
include_once "libs/php-jwt/SignatureInvalidException.php";
include_once "libs/php-jwt/JWT.php";
include_once "libs/php-jwt/Key.php";

use \Firebase\JWT\JWT;
use \Firebase\JWT\Key;

// Файлы, необходимые для подключения к базе данных
include_once "./Config/Database.php";
include_once "../Objects/User.php";

// Получаем соединение с БД
$database = new Database();
$db = $database->getConnection();

// Создание объекта "User"
$user = new User($db);

// Получаем данные
$data = json_decode(file_get_contents("php://input"));

// Получаем jwt
$jwt = isset($data->jwt) ? $data->jwt : "";

// Если JWT не пуст
if ($jwt) {

    // Если декодирование выполнено успешно, показать данные пользователя
    try {

        // Декодирование jwt
        $decoded = JWT::decode($jwt, new Key($key, 'HS256'));

        // Нам нужно установить отправленные данные (через форму HTML) в свойствах объекта пользователя
        $user->firstname = $data->firstname;
        $user->lastname = $data->lastname;
        $user->email = $data->email;
        $user->password = $data->password;
        $user->id = $decoded->data->id;

        // Создание пользователя
        if ($user->update()) {
            // сгенерировать заново JWT здесь
        }

        // Сообщение, если не удается обновить пользователя
        else {

            // Код ответа
            http_response_code(401);

            // Показать сообщение об ошибке
            echo json_encode(array("message" => "Невозможно обновить пользователя"));
        }
    }

    // Если декодирование не удалось, это означает, что JWT является недействительным
    catch (Exception $e) {

        // Код ответа
        http_response_code(401);

        // Сообщение об ошибке
        echo json_encode(array(
            "message" => "Доступ закрыт",
            "error" => $e->getMessage()
        ));
    }
}

// Показать сообщение об ошибке, если jwt пуст
else {

    // Код ответа
    http_response_code(401);

    // Сообщить пользователю что доступ запрещен
    echo json_encode(array("message" => "Доступ закрыт"));
}

Создание метода Update()

Выполним запрос UPDATE, очистку данных и привязку.

Замените комментарий // Здесь будет метод update() в файле api/Objects/User.php следующим кодом.

// Обновить запись пользователя
public function update() {
 
    // Если в HTML-форме был введен пароль (необходимо обновить пароль)
    $password_set=!empty($this->password) ? ", password = :password" : "";
 
    // Если не введен пароль - не обновлять пароль
    $query = "UPDATE " . $this->table_name . "
            SET
                firstname = :firstname,
                lastname = :lastname,
                email = :email
                {$password_set}
            WHERE id = :id";
 
    // Подготовка запроса
    $stmt = $this->conn->prepare($query);
 
    // Инъекция (очистка)
    $this->firstname=htmlspecialchars(strip_tags($this->firstname));
    $this->lastname=htmlspecialchars(strip_tags($this->lastname));
    $this->email=htmlspecialchars(strip_tags($this->email));
 
    // Привязываем значения с HTML формы
    $stmt->bindParam(":firstname", $this->firstname);
    $stmt->bindParam(":lastname", $this->lastname);
    $stmt->bindParam(":email", $this->email);
 
    // Метод password_hash () для защиты пароля пользователя в базе данных
    if(!empty($this->password)){
        $this->password=htmlspecialchars(strip_tags($this->password));
        $password_hash = password_hash($this->password, PASSWORD_BCRYPT);
        $stmt->bindParam(":password", $password_hash);
    }
 
    // Уникальный идентификатор записи для редактирования
    $stmt->bindParam(":id", $this->id);
 
    // Если выполнение успешно, то информация о пользователе будет сохранена в базе данных
    if($stmt->execute()) {
        return true;
    }
 
    return false;
}

Повторная генерация JWT

Замените комментарий // Сгенерировать заново JWT здесь в файле api/update_user.php следующим кодом:

// Нам нужно заново сгенерировать JWT, потому что данные пользователя могут отличаться
$token = array(
   "iss" => $iss,
   "aud" => $aud,
   "iat" => $iat,
   "nbf" => $nbf,
   "data" => array(
       "id" => $user->id,
       "firstname" => $user->firstname,
       "lastname" => $user->lastname,
       "email" => $user->email
   )
);

$jwt = JWT::encode($token, $key, 'HS256');
 
// Код ответа
http_response_code(200);
 
// Ответ в формате JSON
echo json_encode(
    array(
        "message" => "Пользователь был обновлён",
        "jwt" => $jwt
    )
);

Тест успешного обновления пользователя

Введём следующий URL в POSTMAN.

http://authentication-jwt/api/update_user.php

В разделе Body изменим информацию о текущем пользователе с помощью веб-токена JSON, который мы получили ранее (и сохранили).

{
  "firstname" : "Vasiliy",
  "lastname" : "Ivanov",
  "email" : "vasya2@coder.com",
  "password" : "888",
  "jwt" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxMzU2OTk5NTI0LCJuYmYiOjEzNTcwMDAwMDAsImRhdGEiOnsiaWQiOiIyIiwiZmlyc3RuYW1lIjoiVmFzaWxpeSIsImxhc3RuYW1lIjoiSXZhbm92IiwiZW1haWwiOiJ2YXN5YUBjb2Rlci5jb20ifX0.W4ghyd6D4jRAnOeiT4-wTjdjaGh9EYmJDlssrai6A80"
}

Должно выглядеть примерно так.

JWT Успешное обновление информации о пользователе

При обновлении информации о пользователе происходит генерация нового токена JWT.

Чтобы проверить, не удалось ли обновить пользователя, вы можете просто добавить слово EDITED в правленную JWT, или просто удалить JWT. Это должно выглядеть следующим образом.

JWT Ошибка обновления информации о пользователе

8. Создание интерфейса для регистрации пользователей

Мы будем использовать API, которые мы создали ранее. Весь необходимый код будет в одном файле index.html с использованием HTML, CSS и JavaScript.

Откройте файл index.html или создайте, если ещё этого не сделали, в нашей папке authentication-jwt. Поместите следующий код:

<!doctype html>
<html lang="ru">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>Пример REST API аутентификации</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
        crossorigin="anonymous" />
    <link rel="stylesheet" href="custom.css" />
</head>

<body>

    <!-- Навигация -->
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
        <a class="navbar-brand" href="#">Навигация</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
            aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
            <div class="navbar-nav">
                <a class="nav-item nav-link" href="javascript:void(0);" id="home">Домашняя страница</a>
                <a class="nav-item nav-link" href="javascript:void(0);" id="update_account">Учетная запись</a>
                <a class="nav-item nav-link" href="javascript:void(0);" id="logout">Выход</a>
                <a class="nav-item nav-link" href="javascript:void(0);" id="login">Вход</a>
                <a class="nav-item nav-link" href="javascript:void(0);" id="sign_up">Регистрация</a>
            </div>
        </div>
    </nav>

    <main role="main" class="container starter-template">

        <div class="row">
            <div class="col">

                <!-- Здесь будут подсказки / быстрые сообщения -->
                <div id="response"></div>

                <!-- Здесь появится основной контент -->
                <div id="content"></div>
            </div>
        </div>

    </main>

    <!-- jQuery & Bootstrap 4 JavaScript -->
    <script src="http://code.jquery.com/jquery-3.4.1.min.js"
        integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
        integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
        crossorigin="anonymous"></script>

    <!-- Здесь будет jQuery код -->

</body>

</html>

Создадим файл custom.css который мы подключили в шапке, со следующим содержимым:

body { padding-top: 45px; }
.starter-template { padding: 35px 22px; }
#logout{ display:none; }

Показать HTML форму регистрации

Когда вы нажмёте на меню Регистрация на навигационной панели, отобразится форма регистрации.

Замените комментарий <!— Здесь будет jQuery код —> следующим кодом.

<script>
    jQuery($ => {

        // Показ формы регистрации
        $(document).on("click", "#sign_up", () => {

            let html = `
                <h2>Регистрация</h2>
                <form id="sign_up_form">
                    <div class="form-group">
                        <label for="firstname">Имя</label>
                        <input type="text" class="form-control" name="firstname" id="firstname" required />
                    </div>
    
                    <div class="form-group">
                        <label for="lastname">Фамилия</label>
                        <input type="text" class="form-control" name="lastname" id="lastname" required />
                    </div>
    
                    <div class="form-group">
                        <label for="email">Email</label>
                        <input type="email" class="form-control" name="email" id="email" required />
                    </div>
    
                    <div class="form-group">
                        <label for="password">Пароль</label>
                        <input type="password" class="form-control" name="password" id="password" required />
                    </div>
    
                    <button type="submit" class="btn btn-primary">Зарегистрироваться</button>
                </form>
            `;

            clearResponse();
            $("#content").html(html);
        });

        // Выполнение кода при отправке формы

        // Показать форму входа при клике на кнопку

        // Удаление всех быстрых сообщений
        function clearResponse() {
            $("#response").html("");
        }

        // Функция showLoginPage()

        // SerializeObject будет здесь
    });
</script>

Нам нужно обработать данные формы, когда она будет отправлена.

Замените комментарий // Выполнение кода при отправке формы следующим кодом.

// Выполнение кода при отправке формы
$(document).on("submit", "#sign_up_form", function () {

    // Получаем данные формы
    const sign_up_form = $(this);
    const form_data = JSON.stringify(sign_up_form.serializeObject());

    // Отправка данных формы в API
    $.ajax({
        url: "api/create_user.php",
        type: "POST",
        contentType: "application/json",
        data: form_data,
        success: result => {

            // В случае удачного завершения запроса к серверу,
            // сообщим пользователю, что он успешно зарегистрировался и очистим поля ввода
            $("#response").html("<div class='alert alert-success'>Регистрация завершена успешно. Пожалуйста, войдите</div>");
            sign_up_form.find("input").val("");
        },
        error: (xhr, resp, text) => {

            // При ошибке сообщить пользователю, что регистрация не удалась
            $("#response").html("<div class='alert alert-danger'>Невозможно зарегистрироваться. Пожалуйста, свяжитесь с администратором</div>");
        }
    });

    return false;
});

9. Создание интерфейса для входа пользователей

Если щёлкнуть на меню «Вход» на панели навигации, отобразится форма входа.

Замените комментарий // Показать форму входа при клике на кнопку следующим кодом.

// Показа формы входа
$(document).on("click", "#login", () => {
    showLoginPage();
});

// При отправке формы входа
$(document).on("submit", "#login_form", function () {

    // Получаем данные формы
    const login_form = $(this);
    const form_data = JSON.stringify(login_form.serializeObject());

    // Отправка данных формы в API
    $.ajax({
        url: "api/login.php",
        type: "POST",
        contentType: "application/json",
        data: form_data,
        success: result => {

            // Сохраним JWT в куки
            setCookie("jwt", result.jwt, 1);

            // Показ домашней страницы и сообщение об успешном входе
            showHomePage();
            $("#response").html("<div class='alert alert-success'>Успешный вход в систему.</div>");

        },
        error: (xhr, resp, text) => {

            // При ошибке сообщим пользователю, что вход в систему не выполнен и очистим поля ввода
            $("#response").html("<div class='alert alert-danger'>Ошибка входа. Email или пароль указан неверно.</div>");
            login_form.find("input").val("");
        }
    });

    return false;
});

// Показ домашней страницы

Замените комментарий // Функция showLoginPage() следующим кодом.

// Функция показывает HTML-форму для входа в систему.
function showLoginPage() {

    // Удаление jwt
    setCookie("jwt", "", 1);

    // Форма входа
    let html = `
        <h2>Вход</h2>
        <form id="login_form">
            <div class="form-group">
                <label for="email">Email адрес</label>
                <input type="email" class="form-control" id="email" name="email" placeholder="Введите email">
            </div>

            <div class="form-group">
                <label for="password">Пароль</label>
                <input type="password" class="form-control" id="password" name="password" placeholder="Введите пароль">
            </div>

            <button type="submit" class="btn btn-primary">Войти</button>
        </form>
    `;

    $("#content").html(html);
    clearResponse();
    showLoggedOutMenu();
}

// Функция setCookie() поможет нам сохранить JWT в файле cookie
function setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
    var expires = "expires=" + d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

// Эта функция сделает меню похожим на опции для пользователя, вышедшего из системы.
function showLoggedOutMenu() {

    // Показать кнопку входа и регистрации в меню навигации
    $("#login, #sign_up").show();
    $("#logout").hide();
}

// Здесь будет функция showHomePage()

Замените комментарий // SerializeObject будет здесь следующим кодом.

// Функция для преобразования значений формы в формат JSON
$.fn.serializeObject = function () {
    let o = {};
    let a = this.serializeArray();
    $.each(a, function () {
        if (o[this.name] !== undefined) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || "");
        } else {
            o[this.name] = this.value || "";
        }
    });
    return o;
};

10. Создание интерфейса домашней страницы

Замените комментарий // Показ домашней страницы следующим кодом.

// Показать домашнюю страницу
$(document).on("click", "#home", () => {
    showHomePage();
    clearResponse();
});

// Показать форму обновления аккаунта

Замените комментарий // Здесь будет функция showHomePage() следующим кодом.

// Функция для показа домашней страницы
function showHomePage() {

    // Валидация JWT для проверки доступа
    const jwt = getCookie("jwt");

    $.post("api/validate_token.php", JSON.stringify({ jwt: jwt })).done(result => {

        // если прошел валидацию, показать домашнюю страницу
        let html = `
            <div class="card">
                <div class="card-header">Добро пожаловать!</div>
                <div class="card-body">
                    <h5 class="card-title">Вы вошли в систему</h5>
                    <p class="card-text">Вы не сможете получить доступ к домашней странице и страницам учетной записи, если вы не вошли в систему</p>
                </div>
            </div>
        `;

        $("#content").html(html);
        showLoggedInMenu();
    })

        // Показать страницу входа при ошибке
        .fail(function (result) {
            showLoginPage();
            $("#response").html("<div class='alert alert-danger'>Пожалуйста войдите, чтобы получить доступ к домашней станице</div>");
        });
}

// Функция поможет нам прочитать JWT, который мы сохранили ранее.
function getCookie(cname) {
    let name = cname + "=";
    let decodedCookie = decodeURIComponent(document.cookie);
    let ca = decodedCookie.split(";");
    for (let i = 0; i < ca.length; i++) {
        let c = ca[i];
        while (c.charAt(0) == " ") {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

// Если пользователь авторизован
function showLoggedInMenu() {

    // Скроем кнопки входа и регистрации с панели навигации и покажем кнопку выхода
    $("#login, #sign_up").hide();
    $("#logout").show();
}

// Здесь будет функция showUpdateAccountForm()

11. Создание интерфейса для страницы аккаунта пользователя

Замените комментарий // Показать форму обновления аккаунта следующим кодом.

$(document).on("click", "#update_account", () => {
    showUpdateAccountForm();
});

// срабатывание при отправке формы «обновить аккаунт»
$(document).on("submit", "#update_account_form", function () {

    // Дескриптор для update_account_form
    const update_account_form = $(this);

    // Валидация JWT для проверки доступа
    const jwt = getCookie("jwt");

    // Получаем данные формы
    let update_account_form_obj = update_account_form.serializeObject()

    // Добавим JWT
    update_account_form_obj.jwt = jwt;

    // Преобразуем значения формы в JSON с помощью функции stringify()
    const form_data = JSON.stringify(update_account_form_obj);

    // Отправка данных формы в API
    $.ajax({
        url: "api/update_user.php",
        type: "POST",
        contentType: "application/json",
        data: form_data,
        success: result => {

            // Сказать, что учетная запись пользователя была обновлена
            $("#response").html("<div class='alert alert-success'>Учетная запись обновлена</div>");

            // Сохраняем новый JWT в cookie
            setCookie("jwt", result.jwt, 1);
        },

        // Показать сообщение об ошибке пользователю
        error: (xhr, resp, text) => {

            if (xhr.responseJSON.message == "Невозможно обновить пользователя") {
                $("#response").html("<div class='alert alert-danger'>Невозможно обновить пользователя</div>");
            }

            else if (xhr.responseJSON.message == "Доступ закрыт") {
                showLoginPage();
                $("#response").html("<div class='alert alert-success'>Доступ закрыт. Пожалуйста войдите</div>");
            }
        }
    });

    return false;
});

// Выйти из системы
$(document).on("click", "#logout", () => {
    showLoginPage();
    $("#response").html("<div class='alert alert-info'>Вы вышли из системы.</div>");
});

Замените комментарий // Здесь будет функция showUpdateAccountForm() следующим кодом.

function showUpdateAccountForm() {

    // Валидация JWT для проверки доступа
    const jwt = getCookie("jwt");

    $.post("api/validate_token.php", JSON.stringify({ jwt: jwt })).done(result => {

        // Если валидация прошла успешно, покажем данные пользователя в форме
        let html = `
            <h2>Обновление аккаунта</h2>
            <form id="update_account_form">
                <div class="form-group">
                    <label for="firstname">Имя</label>
                    <input type="text" class="form-control" name="firstname" id="firstname" required value="${result.data.firstname}" />
                </div>

                <div class="form-group">
                    <label for="lastname">Фамилия</label>
                    <input type="text" class="form-control" name="lastname" id="lastname" required value="${result.data.lastname}" />
                </div>

                <div class="form-group">
                    <label for="email">Email</label>
                    <input type="email" class="form-control" name="email" id="email" required value="${result.data.email}" />
                </div>

                <div class="form-group">
                    <label for="password">Пароль</label>
                    <input type="password" class="form-control" name="password" id="password" />
                </div>

                <button type="submit" class="btn btn-primary">
                    Сохранить
                </button>
            </form>
        `;

        clearResponse();
        $("#content").html(html);
    })

        // В случае ошибки / сбоя сообщите пользователю, что ему необходимо войти в систему, 
        // чтобы увидеть страницу учетной записи
        .fail(result => {
            showLoginPage();
            $("#response").html("<div class='alert alert-danger'>Пожалуйста, войдите, чтобы получить доступ к странице учетной записи</div>");
        });
}

Если вам понравилась данная статья, рекомендую к прочтению:
1) пошаговое руководство по созданию простого REST API в php (часть 1).
2) jQuery + AJAX + JSON + PHP (часть 2).