Фуллстек-проект: пишем серверную часть страницы авторизации на PHP
hard

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Самое сложное и интересное

Сегодня — финальная часть проекта по решению типичной задачи для фулстек-разработчика: сделать страницу авторизации и регистрации на сайте. На всякий случай напомним, кто такой фулстек и что мы уже сделали к этому моменту.

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

Вот что мы уже сделали в этом проекте:

  1. Создали страницы авторизации и регистрации на сайте.
  2. Настроили виртуальное окружение в Докере, чтобы можно было работать дальше с сайтом без переноса данных на сервер.

Теперь осталась финальная часть — написать бэкенд-скрипты и убедиться, что всё работает как нужно.

Исходные данные

Мы продолжим с того же места, где остановились прошлый раз:

  1. Установили LAMP на компьютер.
  2. Собрали и запустили LAMP-сборку в Докере.
  3. Убедились, что контейнер работает, — зашли на http://localhost и увидели стартовую страницу сборки:

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Переводим все страницы на PHP

Для бэкенда мы будем использовать PHP — серверный язык программирования скриптов, который легко подключить к страницам, чтобы управлять их содержимым и доступом к ним. Чтобы на сервере всё отрабатывалось правильно, поменяем все расширения страниц с .html на .php и положим их в папку docker-compose-lamp/www:

Переводим все страницы на PHP

❗️ Также нам нужно заменить в этих файлах ссылки — вместо ссылок на HTML-страницы нужно поставить ссылки на их PHP-версии.

Откроем в браузере файл register.php — для этого перейдём по адресу http://localhost/register.php:

Переводим все страницы на PHP

Страница открывается, все элементы на месте — это значит, что наше виртуальное окружение с PHP работает и мы можем продолжать настраивать серверную часть.

Создаём базу данных

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

В нашей LAMP-сборке уже есть база данных с именем docker — мы будем использовать её. Чтобы получить доступ к базе, открываем файл sample.env, который лежит в папке docker-compose-lamp:

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Открываем этот файл в любом редакторе кода и смотрим в самый конец файла — находим там пароль суперпользователя (tiger) и название базы (docker):

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Находим, как называется контейнер базой данных в Докере, — пишем в командной строке docker container ls

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Видно, что для доступа к базе данных нам нужно обратиться к контейнеру lamp-mysql8. Теперь начинаем работать с базой данных — запускаем её в командном режиме и создадим нужную таблицу. В командной строке пишем:

docker exec -it lamp-mysql8 mysql -uroot -p

Вводим пароль суперпользователя (по умолчанию — tiger) и попадаем в пошаговый режим работы с MySQL. Вставляем туда такой код и нажимаем энтер — там мы создадим таблицу users с нужными полями:

USE docker;
CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(75) NOT NULL,
  `password` varchar(255) NOT NULL,
  `email` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Как всё будет готово — вводим команду exit; для завершения работы с базой данных.

Создаём файл настроек для работы с базой данных

Чтобы PHP на сервере мог работать с базой данных, нужно объяснить ему, как подключаться к базе: какие там логин, пароль и название базы. Но сложность в том, что нам нужно будет указать имя сервера не локального, а как он называется внутри Докера. Для этого снова заходим в базу данных

docker exec -it lamp-mysql8 mysql -uroot -p

И пишем команду SHOW VARIABLES WHERE Variable_name = 'hostname';

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Копируем значение отсюда — оно нам пригодится на следующем шаге. 

Теперь создаём в том же каталоге www файл config.php и заполняем его всеми данными:

<?php
define('DBSERVER', '5fe7130adcda'); // сервер с базой данных
define('DBUSERNAME', 'root'); // имя пользователя
define('DBPASSWORD', 'tiger'); // пароль
define('DBNAME', 'docker'); // название базы
 
/* соединяемся с базой */
$db = mysqli_connect(DBSERVER, DBUSERNAME, DBPASSWORD, DBNAME);
 
// проверяем соединение
if($db === false){
    die("Ошибка соединения с базой. " . mysqli_connect_error());
}
?>

Создаём файлы сессии и её завершения

Сессия — это когда пользователь входит на сайт под своими логином и паролем. Чтобы при переходе по внутренним страницам сайта сервер не просил пользователя каждый раз их вводить, сервер запоминает состояние сессии. Мы тоже научим сервер делать такое — создадим для этого файл session.php и добавим туда нужный код. Логика будет простая: начинаем сессию и, если логин и пароль правильные, — показываем внутреннюю страницу сайта.

<?php
// начинаем новую сессию
session_start();

// если логин и пароль верны — показываем стартовую внутреннюю страницу
if (isset($_SESSION["userid"]) && $_SESSION["userid"] === true) {
    header("location: welcome.php");
    exit;
}
?>

Когда пользователь нажмёт кнопку «выход», нам нужно закончить сессию. Для этого создадим файл logout.php:

<?php
// стартуем сессию
session_start();

// закрываем сессию
if (session_destroy()) {
    // перекидываем пользователя на страницу ввода логина и пароля
    header("Location: login.php");
    exit;
}
?>

Настраиваем скрипт формы регистрации

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

Начнём с простого — добавим в файл register.php перед формой регистрации такую строчку: она отвечает за вывод ошибок или за сообщение об успешной регистрации:

<?php echo $error; ?>

Теперь добавим в самое начало этого файла PHP-скрипт. Мы добавили комментарии, чтобы было понятно, что там происходит:

<?php

// подключаем служебные файлы, которые создали раньше
require_once "config.php";
require_once "session.php";
// сообщение об ошибке, на старте — пустое
$error ='';


// если на странице нажали кнопку регистрации
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {

    $error ='Ошибка';
    // берём данные из формы
    $fullname = trim($_POST['name']);
    $email = trim($_POST['email']);
    $password = trim($_POST['password']);
    $confirm_password = trim($_POST["confirm_password"]);
    $password_hash = password_hash($password, PASSWORD_BCRYPT);


    if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
        $error = '';
    // указываем, что почта — это строка
    $query->bind_param('s', $email);
    $query->execute();
    // сначала проверяем, есть ли такой аккаунт в базе
    $query->store_result();
        if ($query->num_rows > 0) {
            $error .= '<p class="error">Пользователь с такой почтой уже зарегистрирован!</p>';
        } else {
            // проверяем требование к паролю
            if (strlen($password ) < 6) {
                $error .= '<p class="error">Пароль не должен быть короче 6 символов.</p>';
            }

            // проверяем, ввели ли пароль второй раз
            if (empty($confirm_password)) {
                $error .= '<p class="error">Пожалуйста, подтвердите пароль.</p>';
            } else {
                // если пароли не совпадают
                if (empty($error) && ($password != $confirm_password)) {
                    $error .= '<p class="error">Введённые пароли не совпадают.</p>';
                }
            }
            // если ошибок нет
            if (empty($error) ) {
                // добавляем запись в базу данных
                $insertQuery = $db->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?);");
                $insertQuery->bind_param("sss", $fullname, $email, $password_hash);
                $result = $insertQuery->execute();
                // если всё прошло успешно
                if ($result) {
                    $error .= '<p class="success">Вы успешно зарегистрировались!</p>';
                // если случилась ошибка
                } else {
                    $error .= '<p class="error">Ошибка регистрации, что-то пошло не так.</p>';
                }
            }
        }
    }
    // закрываем соединение с базой данных
    mysqli_close($db);
}
?>

Для проверки зарегистрируем тестового пользователя с логином test@thecode.media. После нажатия кнопки браузер очистит поля в форме и выдаст сообщение о том, что мы успешно зарегистрировались:

Настраиваем скрипт формы регистрации

Посмотрим, как обновилась таблица в базе данных MySQL, — перейдём в ней в режим команд и выполним SELECT * FROM users;

Фуллстек-проект: пишем серверную часть страницы авторизации на PHP

Видно, что в MySQL появилась новая запись и сгенерированный хеш пароля — значит, наша форма регистрации и скрипт работают.

Настраиваем страницу входа

Теперь, когда у нас есть логин и пароль, настроим скрипт на странице входа — там, где мы их вводим. Сделаем так, чтобы после успешного ввода мы попадали на внутреннюю страницу сайта. Логика будет такой же: добавляем перед формой строчку <?php echo $error; ?>, а остальной скрипт добавляем в самое начало файла login.php:

<?php

require_once "config.php";
require_once "session.php";


$error = '';
// если нажата кнопка входа
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {

    $email = trim($_POST['email']);
    $password = trim($_POST['password']);

    // если не указана почта
    if (empty($email)) {
        $error .= '<p class="error">Введите адрес электронной почты.</p>';
    }

    // если не указан пароль
    if (empty($password)) {
        $error .= '<p class="error">Введите пароль.</p>';
    }

    // если ошибок нет
    if (empty($error)) {
        // берём данные пользователя
        if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
            $query->bind_param('s', $email);
            $query->execute();
            $row = $query->get_result()->fetch_assoc();
            // смотрим, есть ли такой пользователь в базе
            if ($row) {
                // если пароль правильный
                if (password_verify($password, $row['password'])) {
                    // стартуем новую сессию
                    $_SESSION["userid"] = $row['id'];
                    $_SESSION["user"] = $row;
                    // перенаправляем пользователя на внутреннюю страницу
                    header("location: welcome.php");
                    exit;
                // если пароль не подходит
                } else {
                    $error .= '<p class="error">Введён неверный пароль.</p>';
                }
            // если пользователя нет в базе
            } else {
                $error .= '<p class="error">Нет пользователя с таким адресом электронной почты.</p>';
            }
        }
    }
    // закрываем соединение с базой данных
    mysqli_close($db);
}
?>

Обновляем страницу и сначала пробуем зайти с неправильными данными:

Настраиваем страницу входа

А теперь — с правильными:

Настраиваем страницу входа

Добавляем сессию во внутренний файл welcome.php

Чтобы неавторизованный пользователь не мог получить доступ к внутренней странице, добавим проверку на активную сессию: если её нет, то перенаправляем пользователя на страницу входа. Для этого в начало файла welcome.php добавим такой скрипт:

<?php
// стартуем сессию
session_start();

// если пользователь не авторизован, то перенаправляем его на страницу входа
if (!isset($_SESSION["userid"]) || $_SESSION["userid"] !== true) {
    header("location: login.php");
    exit;
}
?>

Теперь неавторизованный пользователь не сможет получить доступ к нашей внутренней странице — его сразу перекинет на страницу авторизации. Это значит, что мы справились с нашей фулстек-задачей: сделать и настроить страницу авторизации и регистрации на сайте. Поздравляем с успешным финалом проекта!

<?php

// подключаем служебные файлы, которые создали раньше
require_once "config.php";
require_once "session.php";
// сообщение об ошибке, на старте — пустое
$error ='';


// если на странице нажали кнопку регистрации
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {

    $error ='Ошибка';
    // берём данные из формы
    $fullname = trim($_POST['name']);
    $email = trim($_POST['email']);
    $password = trim($_POST['password']);
    $confirm_password = trim($_POST["confirm_password"]);
    $password_hash = password_hash($password, PASSWORD_BCRYPT);


    if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
        $error = '';
    // указываем, что почта — это строка
    $query->bind_param('s', $email);
    $query->execute();
    // сначала проверяем, есть ли такой аккаунт в базе
    $query->store_result();
        if ($query->num_rows > 0) {
            $error .= '<p class="error">Пользователь с такой почтой уже зарегистрирован!</p>';
        } else {
            // проверяем требование к паролю
            if (strlen($password ) < 6) {
                $error .= '<p class="error">Пароль не должен быть короче 6 символов.</p>';
            }

            // проверяем, ввели ли пароль второй раз
            if (empty($confirm_password)) {
                $error .= '<p class="error">Пожалуйста, подтвердите пароль.</p>';
            } else {
                // если пароли не совпадают
                if (empty($error) && ($password != $confirm_password)) {
                    $error .= '<p class="error">Введённые пароли не совпадают.</p>';
                }
            }
            // если ошибок нет
            if (empty($error) ) {
                // добавляем запись в базу данных
                $insertQuery = $db->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?);");
                $insertQuery->bind_param("sss", $fullname, $email, $password_hash);
                $result = $insertQuery->execute();
                // если всё прошло успешно
                if ($result) {
                    $error .= '<p class="success">Вы успешно зарегистрировались!</p>';
                // если случилась ошибка
                } else {
                    $error .= '<p class="error">Ошибка регистрации, что-то пошло не так.</p>';
                }
            }
        }
    }
    // закрываем соединение с базой данных
    mysqli_close($db);
}
?>

<!DOCTYPE html>
<html lang="form-group">
    <head>
        <meta charset="UTF-8">
        <title>Регистрация</title>
        <!-- подключаем бутстрап -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    </head>
    <body>
        <!-- вся страница будет в одном контейнере -->
        <div class="container">
            <div class="row">
                <!-- делаем самую простую вёрстку -->
                <div class="col-md-12">
                    <h2>Регистрация</h2>
                    <p>Заполните все поля, чтобы создать новый аккаунт.</p>
                    <?php echo $error; ?>
<!-- форма регистрации -->
<form action="" method="post">
    <!-- поле ввода имени -->
    <div class="form-group">
        <label>Имя</label>
        <input type="text" name="name" class="form-control" required>
    </div>    
    <!-- поле ввода электронной почты -->
    <div class="form-group">
        <label>Электронная почта</label>
        <input type="email" name="email" class="form-control" required />
    </div>    
    <!-- поле ввода пароля -->
    <div class="form-group">
        <label>Пароль</label>
        <input type="password" name="password" class="form-control" required>
    </div>
    <!-- поле повторного ввода пароля -->
    <div class="form-group">
        <label>Повторите пароль</label>
        <input type="password" name="confirm_password" class="form-control" required>
    </div>
    <!-- кнопка отправки данных на сервер -->
    <div class="form-group">
        <input type="submit" name="submit" class="btn btn-primary" value="Зарегистрироваться">
    </div>
    <!-- вход для тех, у кого уже есть аккаунт -->
    <p>Уже зарегистрированы? <a href="login.php">Войдите в систему</a>.</p>
</form>
                </div>
            </div>
        </div>    
    </body>
</html>

<?php

require_once "config.php";
require_once "session.php";


$error = '';
$qq = '';
// если нажата кнопка входа
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {

    $email = trim($_POST['email']);
    $password = trim($_POST['password']);

    // если не указана почта
    if (empty($email)) {
        $error .= '<p class="error">Введите адрес электронной почты.</p>';
    }

    // если не указан пароль
    if (empty($password)) {
        $error .= '<p class="error">Введите пароль.</p>';
    }

    // если ошибок нет
    if (empty($error)) {
        // берём данные пользователя
        if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
            $query->bind_param('s', $email);
            $query->execute();
            $row = $query->get_result()->fetch_assoc();
            // смотрим, есть ли такой пользователь в базе
            if ($row) {
                // если пароль правильный
                if (password_verify($password, $row['password'])) {
                    // начинаем новую сессию
                    $_SESSION["userid"] = $row['id'];
                    $_SESSION["user"] = $row;
                    // перенаправляем пользователя на внутреннюю страницу
                    header("location: welcome.php");
                    exit;
                // если пароль не подходит
                } else {
                    $error .= '<p class="error">Введён неверный пароль.</p>';
                }
            // если пользователя нет в базе
            } else {
                $error .= '<p class="error">Нет пользователя с таким адресом электронной почты.</p>';
            }
        }
    }
    // закрываем соединение с базой данных
    mysqli_close($db);
}
?>


<!DOCTYPE html>
<html lang="ru">
    <head>
        <meta charset="UTF-8">
        <title>Вход</title>
        <!-- подключаем бутстрап -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    </head>
    <body>
        <!-- создаём контейнер -->
        <div class="container">
            <div class="row">
                <!-- указываем стиль адаптивной вёрстки -->
                <div class="col-md-12">
                    <!-- пишем заголовок и пояснительный текст -->
                    <h2>Вход</h2>
                    <p>Введите свою почту и пароль.</p>
                    <?php echo $error; ?>
                    <!-- метод, которым будем работать с формой, — отправлять на сервер -->
                    <form action="" method="post">
                        <!-- поле ввода электронной почты -->
                        <div class="form-group">
                            <label>Электронная почта</label>
                            <input type="email" name="email" class="form-control" required />
                        </div>    
                        <!-- поле ввода пароля -->
                        <div class="form-group">
                            <label>Пароль</label>
                            <input type="password" name="password" class="form-control" required>
                        </div>
                        <!-- кнопка отправки данных на сервер -->
                        <div class="form-group">
                            <input type="submit" name="submit" class="btn btn-primary" value="Войти">
                        </div>
                        <!-- ссылка для тех, у кого ещё нет аккаунта -->
                        <p>Нет аккаунта? <a href="register.php">Создайте его за минуту</a>.</p>
                    </form>
                </div>
            </div>
        </div>    
    </body>
</html>

<?php
// начинаем сессию
session_start();

// если пользователь не авторизован, то перенаправляем его на страницу входа
if (!isset($_SESSION["userid"]) || $_SESSION["userid"] !== true) {
    header("location: login.php");
    exit;
}
?>

<!DOCTYPE html>
<html lang="ru">
    <head>
        <meta charset="UTF-8">
        <title>Добро пожаловать</title>
        <!-- подключаем бутстрап -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    </head>
    <body>
        <!-- создаём контейнер -->
        <div class="container">
            <div class="row">
                <!-- указываем стиль адаптивной вёрстки -->
                <div class="col-md-12">
                    <!-- пишем заголовок и пояснительный текст -->
                    <h2>Привет! Добро пожаловать на сайт.</h2>
                    <p>Внутри сайта может быть любое наполнение — можно оформлять как угодно и добавлять любой контент</p>
                    <p>
                    <!-- кнопка для выхода -->
                    <a href="logout.php" class="btn btn-primary btn-lg active" role="button" aria-pressed="true">Выход</a>
                </p>
                </div>
            </div>
        </div>
    </body>
</html>

Текст:

Михаил Полянин

Редактор:

Михаил Полянин

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Мария Дронова

Соцсети:

Юлия Зубарева

Курс «C++ для бэкенда»
Если интересно делать быстрые высоконагруженные системы, посмотрите на программу курса «Яндекс Практикума». Понятная теория, тренажеры, поддержка ревьюеров и обучение в группе, всё как надо.
Посмотреть
Курс «C++ для бэкенда»
Курс «C++ для бэкенда»
Курс «C++ для бэкенда»
Курс «C++ для бэкенда»
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
hard