Чат PHP-разработчиков
Как сделать авторизацию на сайте на PHP: форма входа

Пишем систему авторизации на PHP

Сегодня мы напишем авторизацию пользователя на сайте. Вся система будет работать следующим образом: пользователь вводит логин и пароль на форме входа, если они правильные – в Cookie браузера будет установлена специальная запись – auth token (авторизационный токен). При дальнейших запросах на сервер этот токен будет проверяться и если он будет правильным, то пользователь считается авторизованным.

Первым делом решаем, что страница с формой логина и пароля будет находиться по адресу http://myproject.loc/users/login. Создаём соответствующий роут.

src/routes.php

Код доступен только после покупки курса ООП в PHP.

После этого добавляем новый экшен в контроллере.

src/MyProject/Controllers/UsersController.php

Код доступен только после покупки курса ООП в PHP.

И, наконец, создаём шаблон с формой для этого экшена.

templates/users/login.php

Код доступен только после покупки курса ООП в PHP.

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

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

src/MyProject/Models/Users/User.php

Код доступен только после покупки курса ООП в PHP.

Обратите вниимание – при успешном входе auth token пользователя в базе обновляется – все его предыдущие сессии станут недействительными.

src/MyProject/Controllers/UsersController.php

Код доступен только после покупки курса ООП в PHP.

Проверяем, что ошибки корректно обрабатываются. Для этого пробуем вводить некорректные логин и пароль, а также отправлять форму с пустыми полями.

Нет пользователя с таким email

Неправильный пароль

Отлично, теперь нужно добавить обработку ситуации, когда логин и пароль верны и метод login в модели User вернул нам пользователя.

Создадим специальный сервис, который будет работать с пользовательскими сессиями через Cookie. Назовём его UsersAuthService.

src/MyProject/Services/UsersAuthService.php

Код доступен только после покупки курса ООП в PHP.

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

src/MyProject/Controllers/UsersController.php

Код доступен только после покупки курса ООП в PHP.

Теперь откроем консоль разработчика в Google Chrome и введем правильные логин и пароль. Видим, что нас перекинуло на главную страницу нашего блога, и что была установлена Cookie с именем token.

Авторизационный токен в Cookie

Теперь нам нужно научиться передавать пользователя во View. Согласитесь, пользователь нам понадобится почти на каждой странице сайта, в каждом экшене. И будет неудобно каждый раз при рендеринге шаблона прокидывать пользователя:

src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

Поэтому мы сделаем во View возможность добавлять переменные еще перед рендерингом, вот так:

src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

И теперь мы можем в контроллерах прямо в конструкторах задать нужные переменные.

src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

И добавить в шапке сайта (в шаблонах) вывод пользователя, если он был передан во View:

templates/header.php

Код доступен только после покупки курса ООП в PHP.

Обновим теперь страничку и увидим приветствие.
Приветствие пользователя

Если мы сейчас перейдём на страницу со статьёй, то увидим, что система просит нас залогиниться.
Пользователь не авторизован

Это потому что мы в контроллере статей не прокинули пользователя во View. Давайте добавим тот же код, что и в конструкторе MainController.

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP.

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

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

src/MyProject/Controllers/AbstractController.php

Код доступен только после покупки курса ООП в PHP.

Обратите внимание, свойства user и view теперь с типом protected – они будут доступны в наследниках. Ну а теперь нам достаточно просто отнаследоваться в наших контроллерах от этого класса и можно удалить в них конструкторы и свойства view и user – они будут унаследованы от AbstractController. Это существенно упростит их код.

src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP.

src/MyProject/Controllers/UsersController.php

Код доступен только после покупки курса ООП в PHP.

Теперь можно пройтись по всем страничкам сайта и убедиться, что всё по-прежнему работает. Если теперь нам нужно будет добавить какой-то функционал для всех контроллеров, то мы просто сделаем это в AbstractController.

Ну вот и всё – наша система авторизации готова! Разумеется, есть еще несколько вещей, которые нужно сделать. Их вы реализуете самостоятельно в домашнем задании.

Домашнее задание
  • Если пользователь залогинен, то сделайте рядом с приветствием ссылку для выхода из этой учетной записи. Реализуйте функционал для разлогинивания (роутинг + экшен, удаляющий cookie).
    Разлогин
  • Если пользователь не залогинен, показывайте ссылки для регистрации и входа на сайт.
    Регистрация и вход
Онлайн обучение PHP
Путь с полного нуля до джуниора!
Начать бесплатно
Читайте также
Курс программирования на PHP
Подготовка до уровня устройства на работу!
Начать бесплатно
Комментарии (12)


Sparky

Можешь кинуть код реализации ?

ivashkevich

Код экшена? Или вообще весь?)

Sparky

У меня почему-то не получается разлогинить пользователя, я пытаюсь удалить куки с токеном и после этого вызвать getUserByToken() что бы "удалить" пользователя (null), но безуспешно. Или это по другому делается?

ivashkevich

Просто удаляйте куки и перенаправляйте на другую страницу.

Sparky

так при этом же остается объект пользователя на всех страницах, то есть любой контроллер да и вьюхи будут его видеть и это = что он все еще в системе (у нас же все проверки в шаблонах и контроллерах на !empty($user)). Разве не так?

ivashkevich

Он у вас там берется из cookie. Не будет куки - не будет и юзера.

Kirill.K

Header:

<td colspan="2" style="text-align: right">
            <?php if(!empty($user)): ?>
            Привет, <?= $user->getNickname() ?>  | <a href="http://myproject.loc/users/logOut">Выйти</a>
            <?php else: ?>
            <a href="http://myproject.loc/users/login">Войти</a> | <a href="http://myproject.loc/users/register">Зарегестрироваться</a>
            <? endif; ?>
        </td>

UsersController:

public function logOut()
    {
        setcookie('token', '', -1, '/', '', false, true);
        header('Location: /');
    }

routes:

'~^users/logOut~' => [\MyProject\Controllers\UsersController::class, 'logOut'],
tomsonst
header.php
<tr>
        <td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Привет, ' . $user->getNickname() . ' | <a href = " ../users/exit">Выйти</a>' : '<a href="../users/login">Войдите на сайт</a> | <a href="../users/register">Зарегистрироваться</a>' ?>
        </td>
    </tr>

UsersContoroller.php

public function exit()
{
    setcookie('token', $token, 0, '/', '', false, true);
    header('Location: /');
}

ну и конечно роут '~^users/exit~' => [\MyProject\Controllers\UsersController::class, 'exit'],

AxLT

После успешного логина почему то куки принимают вид:
Set-Cookie: token=3%3A; path=/; HttpOnly
Почему токен так задается не правильно? Из-за этого у меня не определяет юзера и соответственно не работает ничего.
class UsersAuthService:

 /**
     * @param User $user
     */
    public static function createToken(User $user)
    {
        $token = $user->getId() . ':' . $user->refreshAuthToken();
        setcookie('token', $token, 0, '/', '', false, true);
    }

    /**
     * @return User
     */
    public static function getUserByToken(): ?User
    {
        $token = $_COOKIE['token'] ?? '';

        if (empty($token)) {
            return null;
        }

        [$userId, $authToken] = explode(':', $token, 2);

        $user = User::getById((int) $userId);

        if ($user === null) {
            return null;
        }

        if ($user->getAuthToken() !== $authToken) {
            return null;
        }

        return $user;
    }

Models/User.php:

/**
     * @param array $loginData
     * @return User
     * @throws InvalidArgumentException
     */
    public static function login(array $loginData): User
    {
        if (empty($loginData['email'])) {
            throw new InvalidArgumentException('Enter email');
        }
        if (empty($loginData['password'])) {
            throw new InvalidArgumentException('Enter password');
        }
        $user = User::findOneByColumn('email', $loginData['email']);
        if ($user === null) {
            throw new InvalidArgumentException('Can\'t find user with this email');
        }
        if (!password_verify($loginData['password'], $user->getPasswordHash())) {
            throw new InvalidArgumentException('Incorrect password!');
        }
        /*if (!$user->isConfirmed) {
            throw new InvalidArgumentException('Пользователь не подтверждён');
        }*/

        $user->refreshAuthToken();
        $user->save();

        return $user;
    }

    /**
     * @return string
     */
    public function getPasswordHash(): string
    {
        return $this->passwordHash;
    }

    public function refreshAuthToken()
    {
        $this->authToken = sha1(random_bytes(100)) . sha1(random_bytes(100));
    }

    public function getAuthToken()
    {
        return $this->authToken;
    }

UsersController:

public function login()
    {
        if (!empty($_POST)) {
            try {
                $user = User::login($_POST);
                UsersAuthService::createToken($user);
                header('Location: /');
                exit();
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('users/login.php', ['error' => $e->getMessage()]);
                return;
            }
        }

        $this->view->renderHtml('users/login.php');
    }
ivashkevich

Ну что же вы, на 30-ом уроке такие вопросы задавать неуместно. Пора уже самостоятельно находить такие ошибки :) Используйте дебаггер и смотрите, какие куки отправляются с сервера, где и как они формируются.

Самый понятный курс PHP
Онлайн-уроки в удобное время!
Начать бесплатно
Популярное за сутки
Онлайн-курсы PHP и MySQL
Обучение с полного нуля до уровня джуниора!
Начать бесплатно
Сейчас читают
Онлайн-курсы PHP и MySQL
Обучение с полного нуля до уровня джуниора!
Начать бесплатно
Новые статьи
Логические задачи с собеседований