Web JWT-авторизация в веб-приложении на PHP

Статус
В этой теме нельзя размещать новые ответы.

lovskiy

Модератор форума
Сообщения
216
Реакции
145
Баллы
167

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

Шаг 1: Установка библиотеки для работы с JWT:
Используем библиотеку firebase/php-jwt для удобной работы с токенами JWT. Вы можете установить её с помощью Composer:
Bash:
composer require firebase/php-jwt

Шаг 2: Создание функций для генерации и проверки токенов:
Создадим файл jwt_functions.php, который будет содержать функции для генерации и проверки токенов.
PHP:
<?php
require 'vendor/autoload.php';

use Firebase\JWT\JWT;

// Секретный ключ для подписи токена
$secretKey = 'your_secret_key';

// Функция для генерации токена
function generateToken($userId) {
    global $secretKey;
    $tokenId    = base64_encode(random_bytes(32));
    $issuedAt   = time();
    $notBefore  = $issuedAt;
    $expire     = $issuedAt + 3600; // токен действителен 1 час
    $serverName = 'your_server_name'; // измените на имя вашего сервера

    $token = [
        'iat'  => $issuedAt,         // время создания токена
        'jti'  => $tokenId,          // уникальный идентификатор токена
        'iss'  => $serverName,       // издатель токена
        'nbf'  => $notBefore,        // не раньше этого времени
        'exp'  => $expire,           // время истечения токена
        'data' => [
            'userId' => $userId,     // дополнительные данные о пользователе
        ]
    ];

    return JWT::encode($token, $secretKey, 'HS256');
}

// Функция для проверки токена
function validateToken($token) {
    global $secretKey;

    try {
        $decoded = JWT::decode($token, $secretKey, ['HS256']);
        return $decoded->data->userId; // возвращаем идентификатор пользователя
    } catch (\Exception $e) {
        return null; // токен недействителен
    }
}
?>

Шаг 3: Использование функций в веб-приложении:
Используем созданные функции в своем веб-приложении для аутентификации пользователя и защиты ресурсов.
PHP:
<?php
// Подключаем файл с функциями для работы с JWT
require 'jwt_functions.php';

// Пример аутентификации пользователя и генерации токена
$userId = 1; // Идентификатор пользователя из базы данных
$token = generateToken($userId);

// Передаем токен клиенту (например, в виде cookie или заголовка)

// Пример защиты ресурса с использованием токена
$receivedToken = $_COOKIE['auth_token']; // Получаем токен от клиента (пример)

$authenticatedUserId = validateToken($receivedToken);

if ($authenticatedUserId !== null) {
    // Пользователь успешно аутентифицирован, продолжаем выполнение запроса
    echo "Доступ к защищенному ресурсу для пользователя с ID: $authenticatedUserId";
} else {
    // Пользователь не аутентифицирован, возвращаем ошибку или перенаправляем на страницу входа
    echo "Ошибка: Пользователь не аутентифицирован!";
}
?>

Это основы реализации аутентификации и авторизации с использованием токенов JWT на стороне сервера с помощью PHP. Обратите внимание на безопасность и обработку ошибок при добавлении этого метода в ваше веб-приложение.
 

Roberto Reddington

❤️
Пользователь
Сообщения
11,602
Реакции
36,629
Баллы
550
Можно немножечко красивее без всяких global

PHP:
<?php

namespace App;

use stdClass;

class JWT
{
    /**
     * @var string Firebase Secret Key
     */
    private string $secretKey;

    /**
     * Token Expires Time
     */
    private const EXPIRES_TIME = 3600;

    public function __construct(string $secretKey)
    {
        $this->secretKey = $secretKey;
    }

    /**
     * Generate JWT Token
     *
     * @param array $data
     * @return string Token
     */
    public function generateToken(array $data): string
    {
        $now = time();
        $payload = [
            'iat' => $now,
            'iss' => 'http://example.com',
            'nbf' => $now,
            'exp' => $now + self::EXPIRES_TIME,
            'data' => $data,
        ];

        return \Firebase\JWT\JWT::encode($payload, $this->secretKey, 'HS256');
    }

    /**
     * Validate JWT Token
     * @param string $token
     * @return stdClass Token Data
     */
    public function validateToken(string $token): stdClass {
        try {
            return \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->secretKey, 'HS256'))?->data;
        } catch (\Exception $ex) {
            return new stdClass();
        }
    }
}

PHP:
<?php
require_once './vendor/autoload.php';

use App\JWT;

$data = [
    'user' => 'Alex',
];

$jwt = new JWT("secret_key");

// генерируем токен на основе $data
$token = $jwt->generateToken($data);
echo $token;

// проверяем токен
if ($jwt->validateToken($token)) {
    // токен невалидный
} else {
    // токен валидный
}

А вообще целесообразно реализовать интерфейс Guard и внедрять различные методы аутентификации.
 

lovskiy

Модератор форума
Сообщения
216
Реакции
145
Баллы
167
Можно немножечко красивее без всяких global

PHP:
<?php

namespace App;

use stdClass;

class JWT
{
    /**
     * @var string Firebase Secret Key
     */
    private string $secretKey;

    /**
     * Token Expires Time
     */
    private const EXPIRES_TIME = 3600;

    public function __construct(string $secretKey)
    {
        $this->secretKey = $secretKey;
    }

    /**
     * Generate JWT Token
     *
     * @param array $data
     * @return string Token
     */
    public function generateToken(array $data): string
    {
        $now = time();
        $payload = [
            'iat' => $now,
            'iss' => 'http://example.com',
            'nbf' => $now,
            'exp' => $now + self::EXPIRES_TIME,
            'data' => $data,
        ];

        return \Firebase\JWT\JWT::encode($payload, $this->secretKey, 'HS256');
    }

    /**
     * Validate JWT Token
     * @param string $token
     * @return stdClass Token Data
     */
    public function validateToken(string $token): stdClass {
        try {
            return \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->secretKey, 'HS256'))?->data;
        } catch (\Exception $ex) {
            return new stdClass();
        }
    }
}

PHP:
<?php
require_once './vendor/autoload.php';

use App\JWT;

$data = [
    'user' => 'Alex',
];

$jwt = new JWT("secret_key");

// генерируем токен на основе $data
$token = $jwt->generateToken($data);
echo $token;

// проверяем токен
if ($jwt->validateToken($token)) {
    // токен невалидный
} else {
    // токен валидный
}

А вообще целесообразно реализовать интерфейс Guard и внедрять различные методы аутентификации.

Я пишу статьи для начинающих разработчиков (имхо). Ниже изложу минусы Вашего кода на мой взгляд.

1. Сложность использования:
Класс JWT усложняет понимание и использование кода для начинающих разработчиков.

2. Обработка ошибок:
В случае невалидного токена, код возвращает новый объект stdClass, что может затруднить понимание результата проверки.

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

4. Зависимость от библиотеки:
Прямое обращение к библиотеке внутри класса усложняет тестирование и изменение библиотеки будущем.
 

Roberto Reddington

❤️
Пользователь
Сообщения
11,602
Реакции
36,629
Баллы
550
Я пишу статьи для начинающих разработчиков (имхо). Ниже изложу минусы Вашего кода на мой взгляд.

1. Сложность использования:
Класс JWT усложняет понимание и использование кода для начинающих разработчиков.

2. Обработка ошибок:
В случае невалидного токена, код возвращает новый объект stdClass, что может затруднить понимание результата проверки.

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

4. Зависимость от библиотеки:
Прямое обращение к библиотеке внутри класса усложняет тестирование и изменение библиотеки будущем.
1. Я думаю, если разработчик уже работает с jwt, то он по крайней мере уже знаком с концепциями ООП, да и можно сразу подойти к решению вопроса более красиво

2. В данном случае можно оставить без try catch, либо же проверять пустой ли вернулся stdClass, что в принципе не так сложно

3. "захардкожено" оно было вроде в вашем коде

PHP:
$expire     = $issuedAt + 3600;

Мы его вынесли в константу и оно перестало быть "захардкожено"

4. В данном случае encode, decode являются статическими, что означает, что они принадлежат классу, а не экземпляру. Эти методы не сильно влияют на возможности модульного тестирования (юнит-тестирования), единственный минус что их нельзя будет, к примеру, "замокать".Тем не менее, вы можете создать класс-адаптер, такой как FirebaseJWTAdapter и внедрять его в соответствии с паттерном DI

PHP:
namespace App;

use Firebase\JWT\JWT as FirebaseJWT;
use Firebase\JWT\Key;

class FirebaseJWTAdapter
{
    public function encode(array $payload, string $key, string $algorithm): string
    {
        return FirebaseJWT::encode($payload, $key, $algorithm);
    }

    public function decode(string $token, Key $key, array $allowedAlgorithms): \stdClass
    {
        return FirebaseJWT::decode($token, $key, $allowedAlgorithms)->data;
    }
}

PHP:
class JWT
{
    private FirebaseJWTAdapter $jwt;
    // ...
}

Да и думаю, если это для начинающих, то и о тестах тогда речи идти не может

Опять же интересно ваше мнение
 
Статус
В этой теме нельзя размещать новые ответы.
Сверху