Как сделать авторизацию на сайте на PHP

Система авторизации с помощью cookie на PHP

В прошлом уроке мы изучили механизм взаимодействия с cookie в языке PHP. Теперь давайте попробуем применить эти знания на практике — создадим простейшую систему авторизации.

Техническое задание

Начнём мы это дело с описания будущей системы. Пусть у нас будут следующие компоненты:

  1. Главная страница сайта с каким-либо содержимым. Вверху страницы выводится:
    • если пользователь авторизован: Добро пожаловать, %username%.
    • если пользователь неавторизован: Авторизация — слово является ссылкой, которая ведёт на форму авторизации.
      Авторизован пользователь или нет, определяется с помощью cookie.
  2. Страница с формой авторизации. Два инпута для логина и пароля и кнопкой «Вход». Если введены правильные логин и пароль, устанавливаются cookie со значениями переданных данных, а затем пользователя автоматически редиректит (перенаправляет) на главную страницу.
  3. Страница для разлогинивания — при переходе на неё cookie будут удаляться из браузера пользователя, а затем выполняется редирект на главную страницу.

Решение

Продумываем архитектуру

Первое, о чём нам нужно подумать — это то, как будут храниться элементы этой системы, и сколько их вообще будет.

Начнем с простого — для начала у нас должно получиться 3 странички, которые мы описали в ТЗ.

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

Ну и наконец, нам где-то нужно хранить самих пользователей, а именно — их логины и пароли. Создадим для этого простенькую «базу данных» - массив, с набором пар логин-пароль. Это ещё один файл.

Пишем код

Все исходники по данному заданию доступны здесь.

База данных

Ну вот, всё продумали, осталось только написать. Предлагаю начать с нашей базы данных. Создадим файл usersDB.php и запишем в него несколько пользователей.

<?php
return [
    ['login' => 'admin', 'password' => 'P@ssw0rd'],
    ['login' => 'moderator', 'password' => 'password'],
    ['login' => 'user', 'password' => '123'],
];

Функции проверки авторизации

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

<?php
function checkAuth(string $login, string $password): bool 
{
    $users = require __DIR__ . '/usersDB.php';

    foreach ($users as $user) {
        if ($user['login'] === $login 
            && $user['password'] === $password
        ) {
            return true;
        }
    }

    return false;
}

В цикле мы пробегаемся по базе данных пользователей и пытаемся найти пользователя с переданными логином и паролем. Если такой пользователь в массиве найден — возвращаем true. Иначе — false.

Давайте теперь ещё напишем функцию, которая будет возвращать логин текущего пользователя. Эта функция будет проверять текущие значения cookie с ключами login и password с помощью уже существующей функции checkAuth. При этом если пользователь найдётся, то она вернёт его login, а иначе — null. Назовём эту нашу новую функцию getUserLogin.

//продолжение файла auth.php

function getUserLogin(): ?string
{
    $loginFromCookie = $_COOKIE['login'] ?? '';
    $passwordFromCookie = $_COOKIE['password'] ?? '';

    if (checkAuth($loginFromCookie, $passwordFromCookie)) {
        return $loginFromCookie;
    }

    return null;
}

На этом всю логику проверки логина мы описали. Теперь займёмся непосредственно страничками.

Главная страница

Создадим файл index.php. Для простоты примера мы будем использовать только строку с приветствием авторизованного пользователя, либо ссылкой на авторизацию. В этой странице нам потребуется функция проверки авторизации через cookie, поэтому здесь нужно подключить файл auth.php.

<?php
require __DIR__ . '/auth.php';
$login = getUserLogin();
?>
<html>
<head>
    <title>Главная страница</title>
</head>
<body>
<?php if ($login === null): ?>
<a href="/login.php">Авторизуйтесь</a>
<?php else: ?>
Добро пожаловать, <?= $login ?>
<br>
<a href="/logout.php">Выйти</a>
<?php endif; ?>
</body>
</html>

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

Главная страничка с призывом авторизации

Форма авторизации

Давайте теперь сделаем форму авторизации — создаём файл login.php и для начала набрасываем саму HTML-форму. Шаблон получился следующим.

<html>
<head>
    <title>Форма авторизации</title>
</head>
<body>
<form action="/login.php" method="post">
    <label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
    <br>
    <label for="password">Пароль: </label><input type="password" name="password" id="password">
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

Давайте теперь добавим логику проверки переданных данных.

<?php
if (!empty($_POST)) {
    require __DIR__ . '/auth.php';

    $login = $_POST['login'] ?? '';
    $password = $_POST['password'] ?? '';

    if (checkAuth($login, $password)) {
        setcookie('login', $login, 0, '/');
        setcookie('password', $password, 0, '/');
        header('Location: /index.php');
    } else {
        $error = 'Ошибка авторизации';
    }
}
?>
<html>
<head>
    <title>Форма авторизации</title>
</head>
<body>
<?php if (isset($error)): ?>
<span style="color: red;">
    <?= $error ?>
</span>
<?php endif; ?>
<form action="/login.php" method="post">
    <label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
    <br>
    <label for="password">Пароль: </label><input type="password" name="password" id="password">
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

Логика простейшая — если был отправлен POST-запрос, проверяем правильные ли логин и пароль были переданы.

Если нет — то создаём переменную $error, в которой пишем об ошибке авторизации. Позже в шаблоне выводим эту ошибку, если эта переменная объявлена.

Если же авторизация прошла успешно, мы устанавливаем cookie с ключами login и password, в которые помещаем значения из POST-запроса. После этого выполняем редирект на главную страницу.

Редирект делается с помощью заголовка в HTTP-ответе. Этот заголовок называется Location и выглядит следующим образом:

Location: адрес_на_который_нужно_перейти

Для формирования заголовков в PHP используется функция header – ознакомиться с ней более детально вы можете здесь.

Теперь можно попробовать нашу страничку в действии. Давайте для начала введём несуществующую пару логина и пароля. Например, 123:123.

Ошибка при авторизации

Мы увидим соответствующую ошибку.

Теперь давайте зайдем под пользователем user. В нашей БД для него указан пароль 123. Пробуем...

Успешная авторизация

Успех! Нас автоматически перекинуло на главную страницу, где мы видим приветствие для данного пользователя!

Безопасная система авторизации

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

Хеширование паролей

В более совершенных системах авторизации используют хеш от пароля.

Если по-простому, то это такое вычисленное значение, полученное в результате выполнения над паролем определенных манипуляций. В результате этих действий мы получаем строку, из которой нельзя восстановить исходный пароль.

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

Для чего это делается? Да просто потому, что если сайт будет каким-то образом взломан, то злоумышленник в базе данных не найдёт паролей в открытом виде - только хеши. А так как из хеша получить пароль довольно сложно (при условии, что хеш-функция надежна и используется надёжный пароль), то пароль он не узнает. Следовательно:

  1. злоумышленник не сможет использовать пароль для входа на взломанный сайт;
  2. он также не сможет использовать этот пароль для входа под тем же логином и паролем в другие места (ведь довольно часто люди используют одинаковые пароли для всего).

Вычисляются хеши с помощью хеш-функции. Хеш-функции при этом вычисляют хеши следуя алгоритмам хеширования. Сейчас в PHP для хеширования следует использовать функцию password_hash(), а для проверки хеша - password_verify(). Если вы в каком-то уроке увидите, что для хеширования паролей используется md5 - бегите оттуда, такие хеши вскрываются за несколько минут, она устарела ещё лет 10 назад.

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

Помимо хеша пароля в базе данных так же принято хранить так называемые авторизационные токены (AuthToken). Это комбинация символов (желательно подлиннее и с кучей кракозябр), которая генерируется при успешной авторизации пользователя и сохраняется в базе данных. А ещё она и пользователю отправляется.

И потом пользователь с помощью cookie передает этот токен на сервер, где он сравнивается со значением в базе данных. Если они равны, то считаем пользователя авторизованным. Для чего? Дело в том, что куки могут быть похищены злоумышленниками (очень многими способами, не будем об этом в этой статье, кому интересно - погуглите). И если злоумышленнику попадет в руки токен - он не сможет получить исходный пароль. О том, почему это так важно, я уже объяснил.

Заключение

Повторюсь, два последних параграфа здесь только для ознакомления. Реализуем мы их в дальнейших уроках, когда будем знать чуть больше вещей. Пока что - вот такая простейшая но небезопасная система авторизации.

Ах да, чуть не забыл, все исходники к каждому уроку я для вашего удобства буду выкладывать на github – вот тут.

Присоединяйтесь к нам в Twitter, ВКонтакте, Facebook и Google+, а также вступайте в наш чат в Telegram, чтобы не пропустить новые уроки и быть в курсе последних новостей из мира веб-разработки.
Домашнее задание

  • Создайте страницу для разлогинивания - logout.php. При переходе на неё должны удаляться cookie с ключами login и password и выполняться редирект на главную страницу. В качестве ответа предоставьте полный код файла logout.php.
  • Сейчас при переходе авторизованного пользователя на страницу login.php открывается форма авторизации. Сделайте предварительную проверку того, что пользователь уже авторизован. И если он является авторизованным, перенаправляйте его на главную страницу. В качестве ответа на это задание предоставьте только код, который вы добавили в файл login.php.