Обмен данными между клиентом и сервером является фундаментальным аспектом большинства веб-приложений. Одним из способов обеспечить безопасный обмен данными является использование JSON Web Tokens (JWT). JWT представляет собой закодированную строку, которая содержит некоторые данные, позволяющие определить подлинность запроса и безопасно передать информацию между сторонами. В статье разберем структуру и принцип работы JWT, а также рассмотрим его применение на практическом примере.
Структура JWT
JWT состоит из трёх основных частей, которые разделены между собой точками. Эти части называются header (заголовок), payload (полезная нагрузка) и signature (подпись). Схематично структуру токена можно изобразить так:

Разберем каждую часть.
Header
Заголовок содержит JSON-объект с метаданными, которые необходимы для вычисления подписи (signature):
{ "alg": "HS256", "typ": "JWT" }
Эти метаданные включают тип токена (type
), который равен JWT
, а также используемый алгоритм шифрования (alg
). Обычно таким алгоритмом является HMAC SHA256
(HS256
) или RSA
. HMAC SHA-256
использует симметричный ключ, что делает его быстрее. RSA SHA-256
(RS256
), с другой стороны, основан на асимметричных ключах, что позволяет достичь более высокого уровня защиты.
Payload
В payload содержится основная информация (клеймы, claims), которую необходимо передать.
Существуют зарезервированные клеймы, стандартизированные в спецификации, такие как iss
(издатель токена), exp
(время истечения срока действия токена), sub
(тема токена), aud
(получатель токена) и другие. Они не являются обязательными, но широко используются. Эти клеймы помогают управлять жизненным циклом токена и его назначением. Полный список зарезервированных клеймов можно найти, например, здесь.
Кроме зарезервированных, можно добавлять собственные клеймы для передачи любой необходимой информации, например:
{ "user_id": 26, "role": "admin", "theme": "dark" }
Стоит отметить, что добавление слишком большого количества клеймов увеличивает размер токена. Это может негативно влиять на производительность приложения. Поэтому рекомендуется включать в токен только действительно необходимую информацию.
Signature
Подпись JWT создаётся для обеспечения целостности данных токена и подтверждения его подлинности.
Процесс создания подписи:
Header
иpayload
кодируются с помощью алгоритмаBase64Url
.- Это закодированные части токена присоединяются друг к другу с помощью точки.
- Полученная строка подписывается с использованием секретного ключа и указанного в заголовке алгоритма (например,
HS256
илиRS256
).
// HMACSHA256 — выбранный алгоритм подписи (может быть и другим, например, RSA). // secret — секретный ключ, известный только серверу HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Если содержимое токена изменится, подпись станет недействительной, и токен будет отвергнут.
Принцип работы JWT
Принцип коммуникации между сервером и клиентом с помощью JWT происходит следующим образом:
- Пользователь отправляет запрос со своими учетными данными (например, логином и паролем) на сервер.
- Сервер проверяет переданные данные. Если они верны, он генерирует JWT и отправляет токен клиенту.
- Клиент получает JWT и сохраняет его в локальном хранилище либо cookies.
- При каждом последующем запросе на сервер клиент добавляет токен в HTTP-заголовок (обычно в
Authorization: Bearer
). - Сервер декодирует полученный JWT и проверяет его подпись с помощью секретного ключа. Если подпись корректна и токен действителен, запрос считается авторизованным и сервер выполняет его.
Отметим, что JSON Web Token не шифрует данные, а лишь подписывает их. Это означает, что содержимое токена доступно в открытом виде (если не используется дополнительное шифрование), но его нельзя подделать без знания секретного ключа. Любой желающий может декодировать токен, например, с помощью онлайн-инструмента jwt.io, и увидеть его payload
. Поэтому не следует передавать в JWT конфиденциальные данные, такие как пароли или платежные реквизиты, без дополнительной защиты.
Пример авторизации на PHP
Продемонстрируем авторизацию запроса на практическом примере. Создадим форму авторизации. При успешном входе в систему на странице отобразится блок с кнопкой «Отобразить», при нажатии на которую пользователь сможет увидеть информацию о своем платежном балансе.
Технически это будет реализовано следующим образом. Пользователь вводит свои данные в форму и отправляет на сервер. В случае успешной авторизации сервер создает JWT и возвращает клиенту. Теперь при нажатии на кнопку «Отобразить» клиент отправляет запрос с содержащимся в заголовке JWT. Сервер валидирует токен и в случае успеха возвращает информацию о балансе.
Структура примера будет выглядеть так:

Для генерации и проверки JWT нам понадобится библиотека firebase/php-jwt. Мы установим ее с помощью Composer. После установки Composer создаст нам файлы composer.json
и composer.lock
, а также папку vendor
с необходимыми зависимостями.
Для простоты мы не будем хранить информацию о пользователях в базе данных, а создадим для них файл users.php
, который будет возвращаться массив пользователей с их данными:
<?php return [ 'admin' => [ 'id' => 1001, 'password' => password_hash('123456', PASSWORD_DEFAULT), 'role' => 'admin', 'balance' => 1250.75, 'card_last4' => '**** **** **** 4321', 'last_transaction' => '2025-03-10 14:23:33' ], 'user' => [ 'id' => 2002, 'password' => password_hash('qwerty', PASSWORD_DEFAULT), 'role' => 'user', 'balance' => 4280.24, 'card_last4' => '**** **** **** 2573', 'last_transaction' => '2025-03-12 11:46:12' ], ];
Файл jwt.php
будет содержать функции для генерации и валидации JWT:
<?php require 'vendor/autoload.php'; use Firebase\JWT\JWT; use Firebase\JWT\Key; // Функция генерации JWT function generateJwt(array $user): string { // Получаем секретный ключ и тип алгоритма из конфигурационного файла $config = require 'config.php'; // Создаем массив данных для хранения в JWT, включая данные пользователя $payload = [ 'iat' => time(), 'exp' => time() + 3600, 'user' => $user ]; // Генерируем JWT и возвращаем return JWT::encode($payload, $config['secret_key'], $config['algorithm']); } // Функция извлечения данных из JWT function verifyJwt(string $token): ?stdClass { // Получаем секретный ключ и тип алгоритма из конфигурационного файла $config = require 'config.php'; try { // Извлекаем данные из JWT и возвращаем return JWT::decode($token, new Key($config['secret_key'], $config['algorithm'])); } catch (Exception $e) { return null; } }
Эти функции используют секретный ключ и алгоритм подписи, которые находятся в файле конфигурации config.php
:
<?php return [ 'secret_key' => '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'algorithm' => 'HS256', ];
Главный файл index.php
будет содержать HTML-разметку формы авторизации и блоков для отображения платежного баланса. Для базовой стилизации подключим библиотеку Bootstrap в шапке страницы. Выполнять запросы будем асинхронно с помощью Fetch API:
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <title>JWT Авторизация</title> </head> <body class="bg-light"> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-5"> <div class="card p-4 shadow-sm" id="loginCard"> <h3 class="mb-3">Авторизация</h3> <div class="mb-3"> <input type="text" id="username" class="form-control" placeholder="логин"> </div> <div class="mb-3"> <input type="password" id="password" class="form-control" placeholder="пароль"> </div> <button id="loginBtn" class="btn btn-primary">Войти</button> </div> <div class="card p-4 shadow-sm mt-4 d-none" id="logoutCard"> <button id="logoutBtn" class="btn btn-secondary mt-2">Выйти</button> </div> <div class="card p-4 shadow-sm mt-4 d-none" id="userCard"> <h5>Баланс</h5> <p id="userData"></p> <button id="getData" class="btn btn-success mt-3">Отобразить</button> <p class="mt-3 p-3 bg-dark text-white rounded" id="result" style="display: none;"></p> </div> </div> </div> </div> <script> // Обработчик нажатия на кнопку "Войти" document.getElementById('loginBtn').addEventListener('click', async () => { // Получаем данные пользователя из формы const username = document.getElementById('username').value; const password = document.getElementById('password').value; // Отправляем на сервер и получаем ответ const response = await fetch('login.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); // Если в ответе содержится сгенерированный токен, значит, авторизация прошла успешно if (data.token) { // Сохраняем JWT и имя пользователя в локальном хранилище localStorage.setItem('demo_jwt', data.token); localStorage.setItem('demo_user_name', data.user.username); // Показываем нужный UI при авторизованном пользователе setUIByAuthorizationStatus(); } else { alert('Неверные данные авторизации'); } }); // Обработчик нажатия на кнопку "Отобразить" document.getElementById('getData').addEventListener('click', async () => { const token = localStorage.getItem('demo_jwt'); if (!token) { alert('Сперва необходимо пройти авторизацию'); return; } // Устанавливаем токен в заголовок, отправляем запрос на сервер и получаем ответ const response = await fetch('protected.php', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); // Если запрос успешно обработан, показываем данные баланса if (response.status === 200) { document.getElementById('result').style.display = 'block'; document.getElementById('result').innerHTML = ` <ul class="list-group mt-2"> <li class="list-group-item"><strong>Balance:</strong> $${data.data.balance}</li> <li class="list-group-item"><strong>Card:</strong> ${data.data.card_last4}</li> <li class="list-group-item"><strong>Last Transaction:</strong> ${data.data.last_transaction}</li> </ul> `; } else { alert('Доступ запрещен'); } }); // Отобразим блоки правильно при перезагрузке страницы window.addEventListener('DOMContentLoaded', () => { setUIByAuthorizationStatus(); }); // Очистим локальное хранилище при нажатии на кнопку "Выйти" и перезагрузим страницу document.getElementById('logoutBtn').addEventListener('click', () => { localStorage.removeItem('demo_jwt'); localStorage.removeItem('demo_user_name'); location.reload(); }); // Функция для отображения блоков, в зависимости от статуса авторизации пользователя function setUIByAuthorizationStatus() { const token = localStorage.getItem('demo_jwt'); const userName = localStorage.getItem('demo_user_name'); if (token) { document.getElementById('loginCard').classList.add('d-none'); document.getElementById('logoutCard').classList.remove('d-none'); document.getElementById('userCard').classList.remove('d-none'); document.getElementById('userData').innerHTML = ` Пользователь: ${userName} `; } else { document.getElementById('loginCard').classList.remove('d-none'); document.getElementById('logoutCard').classList.add('d-none'); document.getElementById('userCard').classList.add('d-none'); document.getElementById('userData').innerHTML = ''; } } </script> </body> </html>
Для обработки процесса входа пользователя в систему будем использовать файл login.php
:
<?php require 'jwt.php'; // В демонстрационных целях возьмем информацию о пользователях из файла вместо базы данных $users = require 'users.php'; // Получаем данные из POST-запроса $data = json_decode(file_get_contents('php://input'), true); $username = $data['username'] ?? ''; $password = $data['password'] ?? ''; // Валидируем имя пользователя и пароль if (isset($users[$username]) && password_verify($password, $users[$username]['password'])) { // При успешной валидации подготавливаем данные для JWT $userData = [ 'id' => $users[$username]['id'], 'username' => $username, 'role' => $users[$username]['role'], ]; // Генерируем JWT $token = generateJwt($userData); // Возвращаем данные пользователя с токеном echo json_encode([ 'token' => $token, 'user' => $userData ]); } else { // Если данные авторизации не валидны, возвращаем ошибку http_response_code(401); echo json_encode(['message' => 'Неверные данные авторизации']); }
Чтобы получить данные о балансе, будем посылать запрос на protected.php
, где будем верифицировать пользователя по JWT:
<?php // Указываем, что будем возвращать JSON header('Content-Type: application/json'); // Подключаем функции для работы с JWT require 'jwt.php'; // Вместо базы данных берем данные пользователей из файла в демонстрационных целях $users = require 'users.php'; // Проверяем наличие заголовка Authorization $headers = getallheaders(); if (!isset($headers['Authorization'])) { http_response_code(401); echo json_encode(['message' => 'Отсутствует токен']); exit; } // Если заголовок Authorization присутствует, разбиваем его на тип (Bearer) и сам токен list($type, $token) = explode(' ', $headers['Authorization'], 2); // Если схема авторизации не Bearer, возвращаем ошибку 401 (Unauthorized) if (strcasecmp($type, 'Bearer') !== 0) { http_response_code(401); echo json_encode(['message' => 'Неверный тип токена']); exit; } try { // Извлекаем даные из токена $decoded = verifyJwt($token); // Получаем имя пользователя $username = $decoded->user->username; // Берем нужные данные из файла с пользователями и подготавливаем ответ $protectedData = [ 'balance' => $users[$username]['balance'], 'card_last4' => $users[$username]['card_last4'], 'last_transaction' => $users[$username]['last_transaction'] ]; // Возвращаем данные echo json_encode(['data' => $protectedData]); } catch (Exception $e) { // Если токен не прошел валидацию, возвращаем ошибку 401 (Unauthorized) http_response_code(401); echo json_encode(['message' => 'Неверный токен', 'error' => $e->getMessage()]); }
Главная страница до авторизации выглядит так:

После процесса авторизации и нажатия на кнопку «Отобразить» результат будет следующим:

Заключение
JSON Web Token — это компактный и безопасный способ передачи информации между сторонами. Он состоит из трех частей: заголовка, полезной нагрузки (payload) и подписи, обеспечивающей целостность данных. Важно понимать, что JWT не шифрует, а лишь подписывает данные, поэтому его нельзя использовать для хранения конфиденциальной информации без дополнительного шифрования. JWT удобен для аутентификации и передачи данных в API, но требует внимательного управления сроком действия и хранения токена на клиенте для обеспечения безопасности.