Регистрация и авторизация пользователей на PHP

Описание простого алгоритма авторизации и регистрации пользователей на PHP, актуального в 2024 году

Дата публикации: 09.01.2024

Регистрация и Авторизация пользователей

Задача: создать форму регистрации и авторизации пользователя с проверкой пользовательских данных. Защитить некоторые узлы сайта от доступа неавторизованных пользователей, а некоторые — разрешить, но закрыть на этих узлах доступ к определенным частям информации.

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

Попутно мы будем создавать классы, которые потребуются для работы всего этого. А именно: для веб-формы, для работы с пользователями. Возможно, для чего-либо ещё (сейчас, начиная писать эту статью, я, пока что, не придумал).

Ну и, так как на дворе уже 2024 год, всё же, применим для всего этого Bootstrap, актуальная версия которого на сегодняшний день 5.3.2. Для чего Bootstrap? Для адаптивной верстки макета. Чтобы наша форма и наши документы адекватно отображались на всех типах экранов.

1. Подключение Bootstrap

Согласно документации Bootstrap, мы можем подключать его без особых заморочек, ничего не скачивая, просто добавив нужный код в блок <head> нашей страницы.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>

2. Форма авторизации пользователя

Отлично. Подключили. Теперь давайте же попробуем сверстать форму авторизации на сайте.


<div class="container my-3">
    <div class="row">
        <div class="col-xxl-8 col-md-10 offset-xxl-2 offset-md-1 bg-light rounded text-dark p-3">
        <h3 class="my-3">Авторизация пользователя</h3>
            <form id="form-auth" class="needs-validation" name="form-auth" action="/users.php" method="post" autocomplete="off" novalidate>
                <div class="my-3">
                    <label for="auth-login">Логин или электропочта:</label>
                    <input type="text" id="auth-login" name="login" class="form-control" placeholder="Ваши логин или электропочта" required />
                    <div class="error invalid-feedback" id="auth-login_error"></div>
                    <div class="help form-text" id="auth-login_help">Напишите логин или адрес электропочты, указанные вами при регистрации на сайте</div>
                </div>
                <div class="my-3">
                    <label for="auth-password">Пароль:</label>
                    <input type="password" id="auth-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                    <div class="error invalid-feedback" id="auth-password_error"></div>
                    <div class="help form-text" id="auth-password_help">Напишите пароль, указанный вами при регистрации на сайте</div>
                </div>
                <div class="my-3">
                    <input type="submit" class="btn btn-primary" id="auth-submit" name="sign-in" value="Войти" />
                </div>
            </form>
        </div>
    </div>
</div>

3. Форма регистрации пользователя

До того как пользователь будет авторизоваться где-либо, ему нужно быть зарегистрированным. Сверстаем форму регистрации.


<div class="content my-3">
    <div class="row">
        <div class="col-xxl-8 col-md-10 offset-xxl-2 offset-md-1 bg-light rounded text-dark p-3">
            <h3 class="my-3">Регистрация пользователя</h3>
            <form id="form-reg" class="needs-validation" name="form-reg" action="/users.php" method="post" autocomplete="off" novalidate>
                <div class="row gy-2 mb-3">
                    <div class="col-md">
                        <label for="reg-login">Логин:</label>
                        <input type="text" id="reg-login" name="login" class="form-control" placeholder="Ваш логин для регистрации" required />
                        <div class="error invalid-feedback" id="reg-login_error">Логин введен неверно</div>
                        <div class="help form-text" id="reg-login_help">Напишите логин для регистрации на сайте</div>
                    </div>
                    <div class="col-md">
                        <label for="reg-email">Электропочта:</label>
                        <input type="email" id="reg-email" name="email" class="form-control" placeholder="Ваш адрес электропочты" required />
                        <div class="error invalid-feedback" id="reg-email_error"></div>
                        <div class="help form-text" id="reg-email_help">Напишите ваш действующий адрес электропочты для регистрации на сайте</div>
                    </div>
                </div>
                <div class="row gy-2 mb-3">
                    <div class="col-md">
                        <label for="reg-password">Пароль:</label>
                        <input type="password" id="reg-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                        <div class="error invalid-feedback" id="reg-password_error"></div>
                        <div class="help form-text" id="reg-password_help">Напишите пароль, для регистрации на сайте</div>
                    </div>
                    <div class="col-md">
                        <label for="reg-password2">Подтверждение пароля:</label>
                        <input type="password" id="reg-password2" name="password2" class="form-control" placeholder="Повторите ваш пароль" required />
                        <div class="error invalid-feedback" id="reg-password2_error"></div>
                        <div class="help form-text" id="reg-password2_help">Повторите пароль для его подтверждения и исключения ошибки</div>
                    </div>
                </div>
                <div class="my-3 d-flex">
                    <input type="submit" class="btn btn-success me-3" id="reg-submit" name="sign-up" value="Зарегистрироваться" />
                    <input type="reset" class="btn btn-danger" id="reg-reset" name="reset" value="Очистить" />
                </div>
            </form>
        </div>
    </div>
</div>

Немного пояснений по HTML-разметке форм.

  1. Для формы авторизации создано лишь одно поле, в которое пользователем записывается либо логин, либо адрес электропочты. Не нужно заставлять пользователя вспоминать, что именно он указывал при регистрации: логин или почту. К тому же, заставлять еще и вспоминать, какой именно логин. Пользователь введет то, что ему удобнее. А уже мы на сервере обеспечим проверку этих данных на соответствие тем, что хранятся в базе данных.
  2. Формам добавлены css-класс .needs-validation и атрибут novalidate. Согласно соответствующему разделу документации Bootstrap, это необходимо для последующей подсветки полей, прошедших, либо не прошедших проверку введенных данных, а также, согласно той же документации, это отключает стандартную проверку полей форм самим браузером. Уже сейчас вы можете попробовать отправить пустые поля формы, и увидите результат.
  3. Все поля форм у нас сейчас не могут быть пустыми. Поэтому к каждому из них добавлен атрибут required.
  4. В ячейках для полей форм под самими полями форм присутствуют контейнеры div с css-классами .error и .invalid-feedback. Эти поля форм скрыты от пользователя до момента валидации формы. Они используются для вывода на экран сообщений о возможных ошибках в полях формы.
  5. Аналогично предыдущему пункту, в ячейки полей форм добавлены контейнеры div с css-классами .help и .form-text. Эти контейнеры используются для подсказок и инструкциям к полям формы. Классы .error и .help к элементам Bootstrap не относятся. Они будут востребованы у нас потом при написании JavaScript-сценариев для обработки форм.

Из той же документации к Bootstrap нам рекомендовано добавить следующий код JavaScript для форм на странице, чтобы вся эта история с валидацией работала. Его мы поместим в блок <head> внутри тега <script> прямо на странице.


    <script>
        (() => {
            'use strict'
            document.addEventListener('DOMContentLoaded', (event) => {
                // Fetch all the forms we want to apply custom Bootstrap validation styles to
                const forms = document.querySelectorAll('.needs-validation')
    
                if (forms)
                {
                    // Loop over them and prevent submission
                    Array.from(forms).forEach(form => {
                        form.addEventListener('submit', event => {
                        if (!form.checkValidity()) {
                            event.preventDefault();
                            event.stopPropagation();
                        }
    
                        form.classList.add('was-validated')
                        }, false);
                    });
                }
            });
        })();
    </script>
    

4. Компактное и гармоничное размещение форм в разметке страницы

Далее. У нас есть две формы, разместим их во вкладках Bootstrap, чтобы HTML-страница выглядела более гармоничной.


<ul class="nav nav-tabs my-3" id="user-action-tabs" role="tablist">
    <li class="nav-item" role="presentation">
        <button class="nav-link active" id="user-auth-tab" data-bs-toggle="tab" data-bs-target="#user-auth-tab-pane" type="button" role="tab" aria-controls="user-auth-tab-pane" aria-selected="true">Авторизация</button>
    </li>
    <li class="nav-item" role="presentation">
        <button class="nav-link" id="user-reg-tab" data-bs-toggle="tab" data-bs-target="#user-reg-tab-pane" type="button" role="tab" aria-controls="user-reg-tab-pane" aria-selected="false">Регистрация</button>
    </li>
</ul>
<div class="tab-content" id="user-action-tabs-content">
    <div class="tab-pane fade show active px-3" id="user-auth-tab-pane" role="tabpanel" aria-labelledby="user-auth-tab-pane" tabindex="0">
        <div class="row">
            <div class="col-xxl-8 col-md-10 bg-light rounded text-dark p-3">
                <h3 class="my-3">Авторизация пользователя</h3>
                <form id="form-auth" class="needs-validation" name="form-auth" action="/users/" method="post" autocomplete="off" novalidate>
                    <div class="my-3">
                        <label for="auth-login">Логин или электропочта:</label>
                        <input type="text" id="auth-login" name="login" class="form-control" placeholder="Ваши логин или электропочта" required />
                        <div class="error invalid-feedback" id="auth-login_error"></div>
                        <div class="help form-text" id="auth-login_help">Напишите логин или адрес электропочты, указанные вами при регистрации на сайте</div>
                    </div>
                    <div class="my-3">
                        <label for="auth-password">Пароль:</label>
                        <input type="password" id="auth-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                        <div class="error invalid-feedback" id="auth-password_error"></div>
                        <div class="help form-text" id="auth-password_help">Напишите пароль, указанный вами при регистрации на сайте</div>
                    </div>
                    <div class="my-3">
                        <input type="submit" class="btn btn-primary" id="auth-submit" name="sign-in" value="Войти" />
                    </div>
                </form>
            </div>
        </div>
    </div>
    <div class="tab-pane fade px-3" id="user-reg-tab-pane" role="tabpanel" aria-labelledby="user-reg-tab-pane" tabindex="0">
        <div class="row">
            <div class="col-xxl-8 col-md-10 bg-light rounded text-dark p-3">
                <h3 class="my-3">Регистрация пользователя</h3>
                <form id="form-reg" class="needs-validation" name="form-reg" action="/users/" method="post" autocomplete="off" novalidate>
                    <div class="row gy-2 mb-3">
                        <div class="col-md">
                            <label for="reg-login">Логин:</label>
                            <input type="text" id="reg-login" name="login" class="form-control" placeholder="Ваш логин для регистрации" required />
                            <div class="error invalid-feedback" id="reg-login_error">Логин введен неверно</div>
                            <div class="help form-text" id="reg-login_help">Напишите логин для регистрации на сайте</div>
                        </div>
                        <div class="col-md">
                            <label for="reg-email">Электропочта:</label>
                            <input type="email" id="reg-email" name="email" class="form-control" placeholder="Ваш адрес электропочты" required />
                            <div class="error invalid-feedback" id="reg-email_error"></div>
                            <div class="help form-text" id="reg-email_help">Напишите ваш действующий адрес электропочты для регистрации на сайте</div>
                        </div>
                    </div>
                    <div class="row gy-2 mb-3">
                        <div class="col-md">
                            <label for="reg-password">Пароль:</label>
                            <input type="password" id="reg-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                            <div class="error invalid-feedback" id="reg-password_error"></div>
                            <div class="help form-text" id="reg-password_help">Напишите пароль, для регистрации на сайте</div>
                        </div>
                        <div class="col-md">
                            <label for="reg-password2">Подтверждение пароля:</label>
                            <input type="password" id="reg-password2" name="password2" class="form-control" placeholder="Повторите ваш пароль" required />
                            <div class="error invalid-feedback" id="reg-password2_error"></div>
                            <div class="help form-text" id="reg-password2_help">Повторите пароль для его подтверждения и исключения ошибки</div>
                        </div>
                    </div>
                    <div class="my-3 d-flex">
                        <input type="submit" class="btn btn-success me-3" id="reg-submit" name="sign-up" value="Зарегистрироваться" />
                        <input type="reset" class="btn btn-danger" id="reg-reset" name="reset" value="Очистить" />
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

5. База данных

Для того, чтобы пользователь мог зарегистрироваться или авторизоваться, его данные нужно где-то хранить. Для этого мы будем использовать СУБД MySQL. Создадим в ней таблицу, в которой будем хранить всю требуемую информацию о пользователе.


    --
    -- Структура таблицы `users`
    --
    
    CREATE TABLE `users` (
      `id` int(10) UNSIGNED NOT NULL,
      `login` char(16) NOT NULL,
      `email` char(32) NOT NULL,
      `password` char(255) NOT NULL,
      `registration_date` timestamp NOT NULL DEFAULT current_timestamp(),
      `active` tinyint(1) UNSIGNED NOT NULL DEFAULT 1,
      `deleted` tinyint(1) UNSIGNED NOT NULL DEFAULT 0
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
    
    --
    -- Индексы сохранённых таблиц
    --
    
    --
    -- Индексы таблицы `users`
    --
    ALTER TABLE `users`
      ADD PRIMARY KEY (`id`),
      ADD KEY `active` (`active`),
      ADD KEY `deleted` (`deleted`);
    
    --
    -- AUTO_INCREMENT для сохранённых таблиц
    --
    
    --
    -- AUTO_INCREMENT для таблицы `users`
    --
    ALTER TABLE `users`
      MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

Немного пояснений по таблице `users`.

  1. В полях `login`, `email`, `password` будут храниться проверенные данные, которые пользователь введёт в соответствующие поля формы.
  2. Длина поля `password` установлена в 255. Это сделано по рекомендации документации к PHP, которая описывает применение функции password_hash().
  3. В поле `registration_date` автоматически при создании учетной записи подставятся дата и время операции, что мы будем считать датой регистрации.
  4. В поле `active` хранится флаг статуса активации учётной записи. Пока что, мы учётные записи по умолчанию будем считать активированными. Но в будущем это изменим.
  5. Поле `deleted` будем использовать для тех учётных записей, которые по какой-то необходимости пришлось удалить. Физически удалять данные незачем без особой на то причины, а вот установить флаг можно.

Теперь можно собрать воедино нашу стартовую страницу сайта — index.php.

6. Стартовая страница сайта

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


<?php
    // Подключаем файл с основными функциями
    require_once('bootstrap.php');
    ?>
    <!DOCTYPE html>
    <html lang="ru">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Авторизация/регистрация. PHP+MySQL+JavaScript,jQuery</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
    </head>
    <body>
        <div class="container-md container-fluid">
            <h1 class="my-3">Добро пожаловать на сайт</h1>
            <p class="h6">
                Вы зашли на сайт примера авторизации и регистрации пользователя. 
            </p>
            <p>
                Сейчас вы <?php echo (checkAuth()) ? "авторизованы" : "не авторизованы";?> на сайте. <br />
                <?php
                if (checkAuth())
                {
                    echo "<p>Ваш логин: <strong>" . userData()['login'] . "</strong>.</p>";
                    echo "<p>Вы можете <a href='/users.php?action=exit'>выйти</a> из системы.</p>";
                }
                else
                {
                    ?>
                    <p>На этом сайте вам доступно:</p>
                    <ul class="list-unstyled">
                        <li>
                            <a href="/users.php">Авторизация и регистрация</a>
                        </li>
                    </ul>
                    <?php
                }
                ?>
            </p> 
        </div>
    </body>
    </html>

Теперь опишем подробнее то, что видим на стартовой странице.

  1. В самой первой строке мы подключаем файл bootstrap.php. В этом файле мы определяем код наших основных функций.
  2. В теле страницы мы с помощью функции checkAuth() выясняем, авторизован ли пользователь. И если он авторизован, показываем его логин, предлагаем ссылку для окончания сеанса работы с сайтом. Если пользователь не авторизован, предлагаем ему перейти в раздел авторизации и регистрации.

7. Раздел авторизации и регистрации

Ссылка на раздел авторизации и регистрации замещена на странице /users.php. Посмотрим на её код.


<?php
    // Подключаем файл с основными функциями
    require_once('bootstrap.php');
    
    // Здесь будет храниться результат обработки форм
    $aFormHandlerResult = [];
    
    // Если была заполнена форма
    if (!empty($_POST['sign-in']) || !empty($_POST['sign-up']))
    {
        // Если заполнена форма авторизации
        if (!empty($_POST['sign-in']))
        {
            $aFormHandlerResult = userAuthentication($_POST);
        }
        // Если заполнена форма регистрации
        elseif (!empty($_POST['sign-up']))
        {
            // Регистрируем пользователя
            $aFormHandlerResult = userRegistration($_POST);
        }
    }
    
    // Если пользователь желает разлогиниться
    if (!empty($_GET['action']) && $_GET['action'] == "exit" && checkAuth())
    {
        // Завершаем сеанс пользователя
        userLogout();
    }
    
    // Если пользователь вводил данные, покажем их ему
    $sLogin = (!empty($aFormHandlerResult['data']['login'])) ? htmlspecialchars_decode($aFormHandlerResult['data']['login']) : "";
    $sEmail = (!empty($aFormHandlerResult['data']['email'])) ? htmlspecialchars_decode($aFormHandlerResult['data']['email']) : "";
    ?>
    <!DOCTYPE html>
    <html lang="ru">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Личный кабинет пользователя. Авторизация/регистрация. PHP+MySQL+JavaScript,jQuery</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
        <script>
            (() => {
                'use strict'
                document.addEventListener('DOMContentLoaded', (event) => {
                    // Fetch all the forms we want to apply custom Bootstrap validation styles to
                    const forms = document.querySelectorAll('.needs-validation');
    
                    if (forms)
                    {
                        // Loop over them and prevent submission
                        Array.from(forms).forEach(form => {
                            form.addEventListener('submit', event => {
                            if (!form.checkValidity()) {
                                event.preventDefault();
                                event.stopPropagation();
                            }
    
                            form.classList.add('was-validated')
                            }, false);
                        });
                    }
                });
            })();
        </script>
    </head>
    <body>
        <div class="container-md container-fluid">
            <h1 class="my-3">Личный кабинет пользователя</h1>
            <p>
                Сейчас вы <?php echo (checkAuth()) ? "авторизованы" : "не авторизованы";?> на сайте. <br />
                <?php
                if (checkAuth())
                {
                    echo "<p>Ваш логин: <strong>" . userData()['login'] . "</strong>.</p>";
                    echo "<p>Вы можете <a href='/users.php?action=exit'>выйти</a> из системы.</p>";
                }
                // Если пользователь вышел из системы
                elseif (!empty($_GET['action']))
                {
                    if ($_GET['action'] == "exit")
                    {
                        echo "Вы успешно вышли из системы";
                    }
                }
    
                // Перенаправим пользователя на главную страницу при успешной авторизации/регистрации
                if (!empty($aFormHandlerResult['success']) && $aFormHandlerResult['success'] === TRUE)
                {
                ?>
                    <script>
                        setTimeout(() => {
                            window.location.href="/";
                        }, 3000);
                    </script>
                <?php
                }
                ?>
            </p>
            <?php
            // Блок с формами авторизации/регистрации показываем только неавторизованным пользователям
            if (!checkAuth())
            {
            ?>
                <ul class="nav nav-tabs my-3" id="user-action-tabs" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php print (empty($aFormHandlerResult) || $aFormHandlerResult['type'] == 'auth') ? "active" : "";?>" id="user-auth-tab" data-bs-toggle="tab" data-bs-target="#user-auth-tab-pane" type="button" role="tab" aria-controls="user-auth-tab-pane" aria-selected="true">Авторизация</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link <?php print (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'reg') ? "active" : "";?>" id="user-reg-tab" data-bs-toggle="tab" data-bs-target="#user-reg-tab-pane" type="button" role="tab" aria-controls="user-reg-tab-pane" aria-selected="false">Регистрация</button>
                    </li>
                </ul>
                <div class="tab-content bg-light" id="user-action-tabs-content">
                    <div class="tab-pane fade px-3 <?php print (empty($aFormHandlerResult) || $aFormHandlerResult['type'] == 'auth') ? "show active" : "";?>" id="user-auth-tab-pane" role="tabpanel" aria-labelledby="user-auth-tab-pane" tabindex="0">
                        <div class="row">
                            <div class="col-xxl-8 col-md-10 rounded text-dark p-3">
                                <!-- Блок для сообщений о результате обработки формы -->
                                <?php
                                // Если была обработана форма
                                if (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'auth')
                                {
                                    $sClass = match($aFormHandlerResult['success']) {
                                                TRUE => "my-3 alert alert-success",
                                                FALSE => "my-3 alert alert-danger"
                                    };
    
                                    ?>
                                    <div class="<?=$sClass?>">
                                        <?=$aFormHandlerResult['message'];?>
                                    </div>
                                    <?php
                                }
                                ?>
                                <h3 class="my-3">Авторизация пользователя</h3>
                                <form id="form-auth" class="needs-validation" name="form-auth" action="/users.php" method="post" autocomplete="off" novalidate>
                                    <div class="my-3">
                                        <label for="auth-login">Логин или электропочта:</label>
                                        <input type="text" id="auth-login" name="login" class="form-control" placeholder="Ваши логин или электропочта" required value="<?php print (empty($aFormHandlerResult) || $aFormHandlerResult['type'] == 'auth') ? $sLogin : "";?>" />
                                        <div class="error invalid-feedback" id="auth-login_error"></div>
                                        <div class="help form-text" id="auth-login_help">Напишите логин или адрес электропочты, указанные вами при регистрации на сайте</div>
                                    </div>
                                    <div class="my-3">
                                        <label for="auth-password">Пароль:</label>
                                        <input type="password" id="auth-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                                        <div class="error invalid-feedback" id="auth-password_error"></div>
                                        <div class="help form-text" id="auth-password_help">Напишите пароль, указанный вами при регистрации на сайте</div>
                                    </div>
                                    <div class="my-3">
                                        <input type="submit" class="btn btn-primary" id="auth-submit" name="sign-in" value="Войти" />
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                    <div class="tab-pane fade px-3 <?php print (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'reg') ? "show active" : "";?>" id="user-reg-tab-pane" role="tabpanel" aria-labelledby="user-reg-tab-pane" tabindex="0">
                        <div class="row">
                            <div class="col-xxl-8 col-md-10 rounded text-dark p-3">
                                <!-- Блок для сообщений о результате обработки формы -->
                                <?php
                                // Если была обработана форма
                                if (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'reg')
                                {
                                    $sClass = match($aFormHandlerResult['success']) {
                                                TRUE => "my-3 alert alert-success",
                                                FALSE => "my-3 alert alert-danger"
                                    };
                                   
                                    ?>
                                    <div class="<?=$sClass?>">
                                        <?=$aFormHandlerResult['message'];?>
                                    </div>
                                    <?php
                                }
                                ?>
                                <h3 class="my-3">Регистрация пользователя</h3>
                                <form id="form-reg" class="needs-validation" name="form-reg" action="/users.php" method="post" autocomplete="off" novalidate>
                                    <div class="row gy-2 mb-3">
                                        <div class="col-md">
                                            <label for="reg-login">Логин:</label>
                                            <input type="text" id="reg-login" name="login" class="form-control" placeholder="Ваш логин для регистрации" required value="<?php print (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'reg') ? $sLogin : "";?>" />
                                            <div class="error invalid-feedback" id="reg-login_error">Логин введен неверно</div>
                                            <div class="help form-text" id="reg-login_help">Напишите логин для регистрации на сайте</div>
                                        </div>
                                        <div class="col-md">
                                            <label for="reg-email">Электропочта:</label>
                                            <input type="email" id="reg-email" name="email" class="form-control" placeholder="Ваш адрес электропочты" required value="<?php print (!empty($aFormHandlerResult) && $aFormHandlerResult['type'] == 'reg') ? $sEmail : "";?>" />
                                            <div class="error invalid-feedback" id="reg-email_error"></div>
                                            <div class="help form-text" id="reg-email_help">Напишите ваш действующий адрес электропочты для регистрации на сайте</div>
                                        </div>
                                    </div>
                                    <div class="row gy-2 mb-3">
                                        <div class="col-md">
                                            <label for="reg-password">Пароль:</label>
                                            <input type="password" id="reg-password" name="password" class="form-control" placeholder="Напишите ваш пароль" required />
                                            <div class="error invalid-feedback" id="reg-password_error"></div>
                                            <div class="help form-text" id="reg-password_help">Напишите пароль, для регистрации на сайте</div>
                                        </div>
                                        <div class="col-md">
                                            <label for="reg-password2">Подтверждение пароля:</label>
                                            <input type="password" id="reg-password2" name="password2" class="form-control" placeholder="Повторите ваш пароль" required />
                                            <div class="error invalid-feedback" id="reg-password2_error"></div>
                                            <div class="help form-text" id="reg-password2_help">Повторите пароль для его подтверждения и исключения ошибки</div>
                                        </div>
                                    </div>
                                    <div class="my-3 d-flex">
                                        <input type="submit" class="btn btn-success me-3" id="reg-submit" name="sign-up" value="Зарегистрироваться" />
                                        <input type="reset" class="btn btn-danger" id="reg-reset" name="reset" value="Очистить" />
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            <?php
            }
            ?>  
        </div>
    </body>
    </html>

Здесь уже кода побольше. В первой строке мы снова подключаем файл bootstrap.php. А вот далее всё немного иначе, чем на стартовой странице сайта.

  1. Инициализируем переменную $aFormHandlerResult, в которой будет храниться результат обработки форм. Обе формы (и авторизации, и регистрации) отправляют данные на обработку в этот же файл сценария.
  2. Была ли заполнена форма мы можем определить на наличию в суперглобальном массиве $_POST параметров ['sign-in'] или ['sign-up']. Далее, если была заполнена форма авторизации, вызываем функцию userAuthentication(), если была заполнена форма регистрации — userRegistration(). В обоих случаях передаем в эти функции содержимое суперглобального массива $_POST. Результат работы этих функций будет сохранен в переменную $aFormHandlerResult.
  3. Если пользователь выходит из системы, мы вызываем функцию userLogout().
  4. На случай, если пользователь при вводе данных ошибся, сохраним значения логина и адреса электропочты, чтобы он не вводил их повторно. Ведь страница будет перезагружена после отправки формы.
  5. Далее на странице идет блок личного кабинета. Авторизованный пользователь видит свой логин и ссылку на выход из системы. Неавторизованному же пользователю будут показаны вкладки с формами авторизации и регистрации.
  6. Над каждой из форм будет размещен блок с сообщением о результате обработки формы, если таковая имела место быть.

Ну а теперь пришла пора описать файл bootstrap.php, где объявлены все наши основные функции.

8. Файл с основными функциями сценария


    <?php
    /**
     * Проверяет статус авторизации пользователя
     * @param string $value может принимать для проверки конкретное значение логина или email
     * @return TRUE | FALSE
     */
    function checkAuth(string $value = "") : bool
    {
        // Статус авторизации пользователя
        $bIsAuth = FALSE;
        
        // Если сессия не запущена, запускаем её
        if (session_status() !== PHP_SESSION_ACTIVE)
        {
            session_start();
        }
    
        // Если не нужно проверить конкретные данные, и данные о пользователе записаны в сессию
        if (!empty($_SESSION['login']) && $value === "")
        {
            // Пользователь авторизован
            $bIsAuth = TRUE;
        }
        // Если нужно проверить конкретные данные
        elseif ($value !== "")
        {
            // Если в сессии сохранены логин или электропочта, а функции переданы значения для проверки, и они совпадают с теми, что хранятся в сессии
            if ((!empty($_SESSION['login']) || !empty($_SESSION['email'])) && ($_SESSION['login'] === $value || $_SESSION['email'] === $value))
            {
                // Пользователь авторизован
                $bIsAuth = TRUE;
            }
            // Если есть попытка подмены данных в сессии
            elseif ((!empty($_SESSION['login']) || !empty($_SESSION['email'])) && $_SESSION['login'] !== $value && $_SESSION['email'] !== $value)
            {
                // Стираем данные из сессии
                unset($_SESSION['login']);
                unset($_SESSION['email']);
                unset($_SESSION['password']);
    
                // Останавливаем работу скрипта
                die("<p>Несоответствие данных авторизации сессии. Работа остановлена</p>");
            }
        }
        
        // Возвращаем результат проверки
        return $bIsAuth;
    }
    
    /**
     * Подключается к СУБД
     */
    function dbConnect()
    {
        // Подключаем файл с конфигурацией для подключения к СУБД
        $aDbConfig = include('config/database.php');
        
        // Если настройки получены
        if (!empty($aDbConfig))
        {
            // Подключаемся к СУБД
            try {
                $dbh = new PDO("mysql:host={$aDbConfig['host']};dbname={$aDbConfig['dbname']}", 
                                $aDbConfig['user'], 
                                $aDbConfig['password'],
                            $aDbConfig['options']);
                
                // Результаты выборки из БД хотим видеть в виде объекта
                $dbh->setAttribute(PDO::FETCH_OBJ, TRUE);
            }
            // Если подключиться не удалось, останавливаем выполнение скрипта
            catch (PDOException $e)
            {
                // Показываем сообщение об ошибке
                die("<p><strong>При подключении к СУБД произошла ошибка:</strong> " . $e->getMessage() . "</p>");
            }
    
            return $dbh;
        }
    }
    
    /**
     * Авторизует пользователя с указанными данными
     * @param array $data
     * @return array
     */
    function userAuthentication(array $data) : array
    {
        // Данные, которые вернёт функция
        $aReturn = [
            'success' => FALSE,
            'message' => "При авторизации пользователя произошла ошибка",
            'data' => [],
            'type' => 'auth'
        ];
    
        // Проверяем, не был ли пользователь ранее авторизован
        if (checkAuth(strval(htmlspecialchars(trim($_POST['login'])))))
        {
            $aReturn = [
                'success' => TRUE,
                'message' => "Вы ранее уже авторизовались на сайте",
                'data' => [],
                'type' => 'auth'
            ];
        }
        // Если авторизации не было
        else 
        {
            try {
                // Если данные не были переданы
                if (empty($data))
                {
                    throw new Exception("Не переданы параметры для авторизации пользователя");
                }
                // Если передаются данные для регистрации или какого-то иного действия
                elseif (empty($data['sign-in']))
                {
                    throw new Exception("Необходимо передать данные для процедуры авторизации пользователя");
                }
            }
            catch (Exception $e)
            {
                die("<p><strong>При вызове функции авторизации пользователя произошла ошибка:</strong> {$e->getMessage()}</p>");
            }
    
            // Обрабатываем данные формы
            $sLogin = strval(htmlspecialchars(trim($_POST['login'])));
            $sPassword = strval(htmlspecialchars(trim($_POST['password'])));
    
            // Определяем тип авторизации: по логину или адресу электропочты
            $sType = NULL;
            $sType = match(validateEmail($sLogin)) {
                        TRUE => 'email',
                        FALSE => 'login'
            };
    
            // Если не передан пароль
            if (empty($sPassword))
            {
                $aReturn['message'] = "Поле пароля не было заполнено";
                $aReturn['data'] = $data;
            }
            else 
            {
                // Ищем соответствие переданной информации в БД
                $dbh = dbConnect();
    
                $stmt = $dbh->prepare("SELECT * FROM `users` WHERE `{$sType}` = :{$sType} AND `deleted` = 0");
    
                // Подготавливаем запрос
                $stmt->bindParam(":{$sType}", $sLogin);
    
                // Выполняем запрос
                $stmt->execute();
    
                // Если были найдены записи
                if ($stmt->rowCount())
                {
                    $oUser = $stmt->fetch(PDO::FETCH_OBJ);
                    
                    /**
                     * Согласно документации к PHP, мы для подготовки пароля пользователя к сохранению в БД
                     * мы использовали функцию password_hash() https://www.php.net/manual/ru/function.password-hash
                     * Теперь для проверки пароля для авторизации нам нужно использовать функцию password_verify()
                     * https://www.php.net/manual/ru/function.password-verify.php
                     */
    
                    // Проверяем пароль пользователя
                    // Если хэш пароля совпадает
                    if (password_verify($sPassword, $oUser->password))
                    {
                        // Авторизуем пользователя
                        if (session_status() !== PHP_SESSION_ACTIVE)
                        {
                            session_start();
                        }
                        
                        $aReturn['success'] = TRUE;
                        $aReturn['message'] = "Вы успешно авторизовались на сайте";
                        $aReturn['data'] = $data;
                        $aReturn['data']['user_id'] = $oUser->id;
    
                        $_SESSION['login'] = $oUser->login;
                        $_SESSION['email'] = $oUser->email;
                        $_SESSION['password'] = $oUser->password;
                    }
                    else
                    {
                        $aReturn['message'] = "Для учетной записи <strong>{$sLogin}</strong> указан неверный пароль";
                        $aReturn['data'] = $data;
                    }
                }
            }
        }
    
        // Возвращаем результат авторизации вызову
        return $aReturn;
    }
    
    /**
     * Регистрирует пользователя с указанными параметрами
     * @param array $data
     * @return array
     */
    function userRegistration(array $data) : array
    {
        // Результат регистрации пользователя
        $bRegistration = FALSE;
    
        // Данные, которые вернёт функция
        $aReturn = [
            'success' => FALSE,
            'message' => "При регистрации пользователя произошла ошибка",
            'data' => [],
            'type' => 'reg'
        ];
    
        try {
            // Если данные не были переданы
            if (empty($data))
            {
                throw new Exception("Не переданы параметры для регистрации пользователя");
            }
            // Если передаются данные для авторизации или какого-то иного действия
            elseif (empty($data['sign-up']))
            {
                throw new Exception("Необходимо передать данные для процедуры регистрации пользователя");
            }
        }
        catch (Exception $e)
        {
            die("<p><strong>При вызове функции регистрации пользователя произошла ошибка:</strong> {$e->getMessage()}</p>");
        }
    
        // Обрабатываем данные формы
        $sLogin = strval(htmlspecialchars(trim($_POST['login'])));
        $sEmail = strval(htmlspecialchars(trim($_POST['email'])));
        $sPassword = strval(htmlspecialchars(trim($_POST['password'])));
        $sPassword2 = strval(htmlspecialchars(trim($_POST['password2'])));
    
        // Проверяем указанный email
        // Если указанные данные не являются корректным email, ничего не делаем дальше
        if (validateEmail($sEmail))
        {
            // Логин и пароли не могут быть пустыми
            if (empty($sLogin))
            {
                $aReturn['message'] = "Поле логина не было заполнено";
                $aReturn['data'] = $data;
            }
            elseif (empty($sPassword))
            {
                $aReturn['message'] = "Поле пароля не было заполнено";
                $aReturn['data'] = $data;
            }
            // Пароли должны быть идентичны
            elseif ($sPassword !== $sPassword2)
            {
                $aReturn['message'] = "Введенные пароли не совпадают";
                $aReturn['data'] = $data;
            }
            // Если логин не уникален
            elseif (isLoginExist($sLogin))
            {
                $aReturn['message'] = "Указанный вами логин ранее уже был зарегистрирован";
                $aReturn['data'] = $data;
            }
            // Если email не уникален
            elseif (isEmailExist($sEmail))
            {
                $aReturn['message'] = "Указанный вами email ранее уже был зарегистрирован";
                $aReturn['data'] = $data;
            }
            // Если все проверки прошли успешно, можно регистрировать пользователя
            else
            {
                $bRegistration = TRUE;
            }
        }
        else
        {
            $aReturn['message'] = "Указанное значение адреса электропочты не соответствует формату";
            $aReturn['data'] = $data;
        }
    
        // Регистрируем нового пользователя
        if ($bRegistration)
        {
            // Подключаемся к СУБД
            $dbh = dbConnect();
    
            // Создаем тело SQL-запроса
            $stmt = $dbh->prepare("INSERT INTO `users` (`login`, `email`, `password`) VALUES (:login, :email, :password)");
    
            /**
             * Согласно документации к PHP, мы для подготовки пароля пользователя к сохранению в БД
             * будем использовать функцию password_hash() https://www.php.net/manual/ru/function.password-hash
             * Причем, согласно рекомендации, начиная с версии PHP 8.0.0 не нужно указывать соль для пароля. Значит, и не будем
             */
            // Хэшируем пароль
            $sPassword = password_hash($sPassword, PASSWORD_BCRYPT);
    
            // Подготавливаем запрос
            $stmt->bindParam(':login', $sLogin);
            $stmt->bindParam(':email', $sEmail);
            $stmt->bindParam(':password', $sPassword);
    
            // Выполняем запрос
            $stmt->execute();
    
            // Если пользователь был успешно сохранен в БД
            if ($dbh->lastInsertId())
            {
                $aReturn['success'] = TRUE;
                $aReturn['message'] = "Пользователь с логином <strong>{$sLogin}</strong> и email <strong>{$sEmail}</strong> успешно зарегистрирован.";
                $aReturn['data']['user_id'] = $dbh->lastInsertId();
    
                // Уничтожаем данные от пользователя
                unset($_POST);
            }
        }
       
        // Возвращаем результат регистрации вызову
        return $aReturn;
    }
    
    // Проверка правильности email
    function validateEmail(string $email) : bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL);
    }
    
    /**
     * Проверяет уникальность логина в системе
     * @param string $login
     * @return TRUE | FALSE
     */
    function isLoginExist(string $login) : bool
    {
        // Подключаемся к СУБД
        $dbh = dbConnect();
    
        // Проверяем уникальность логина в БД
        $sQuery = "SELECT * FROM `users` WHERE `login` = '{$login}' AND `deleted` = '0'";
    
        // Выполняем запрос
        try {
            $sth = $dbh->query($sQuery);
        }
        catch (PDOException $e)
        {
            die("<p><strong>При выполнении запроса произошла ошибка:</strong> {$e->getMessage()}</p>");
        }
        
        // Если логин уникален, в результате запроса не должно быть строк
        return $sth->rowCount() !== 0;
    }
    
    /**
     * Проверяет уникальность email в системе
     * @param string $email
     * @return TRUE | FALSE
     */
    function isEmailExist(string $email) : bool
    {
        // Подключаемся к СУБД
        $dbh = dbConnect();
    
        // Проверяем уникальность логина в БД
        $sQuery = "SELECT * FROM `users` WHERE `email` = '{$email}' AND `deleted` = '0'";
    
        // Выполняем запрос
        try {
            $sth = $dbh->query($sQuery);
        }
        catch (PDOException $e)
        {
            die("<p><strong>При выполнении запроса произошла ошибка:</strong> {$e->getMessage()}</p>");
        }
    
        // Если логин уникален, в результате запроса не должно быть строк
        return $sth->rowCount() !== 0;
    }
    
    // Возвращает данные об авторизованном пользователе
    function userData() : array | NULL
    {
        $return = NULL;
    
        if (checkAuth())
        {
            $return['login'] = $_SESSION['login'];
            $return['email'] = $_SESSION['email'];
        }
    
        return $return;
    }
    
    // Осуществляет выход пользователя из системы
    function userLogout()
    {
        // Запуск сессии, если она не активна
        if (session_status() !== PHP_SESSION_ACTIVE)
        {
            session_start();
        }
    
        // Уничтожение данных о пользователе в сессии
        unset($_SESSION['login']);
        unset($_SESSION['email']);
        unset($_SESSION['password']);
    }
    ?>

Код файла bootstrap.php снабжен обширными комментариями, ознакомление с которыми должно дать четкое понимание, как это всё работает.

Осталось взглянуть лишь на код файла, из которого мы получаем параметры для подключения к СУБД. Это файл config/database.php.


<?php
return [
    'host' => 'localhost',
    'user' => 'demo',
    'password' => 'fy)s@6!9cJ*g!xvZ',
    'dbname' => 'demo_auth_reg_0',
    'options' => [
        PDO::ATTR_PERSISTENT => TRUE,   // включаем постоянные соединения
    ]
];
?>

Заключение

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

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

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

Описанные выше файлы доступны для скачивания.

  • Я опубликовал эту статью:09.01.2024
  • 1 053
  • Яндекс.Метрика

Меню сайта

Settings

Performance

CPU Load
60%
CPU Temparature
42°
RAM Usage
6,532 MB

Customer care

Reports

Projects

May 14, 2020

Upcoming events

12:00

Donec laoreet fringilla justo a pellentesque

13:20

Nunc quis massa nec enim

14:00

Praesent sit amet