Антон Ларичев

Введение
JWT (JSON Web Token) — это открытый стандарт (RFC 7519) для безопасной передачи данных между сторонами в виде JSON-объекта. Токены широко применяются для аутентификации и авторизации: сервер выдаёт токен при входе, а клиент передаёт его с каждым запросом.
В отличие от сессий, JWT не требует хранения состояния на сервере — вся необходимая информация находится в самом токене. Это делает его удобным для микросервисных архитектур, мобильных приложений и REST API.
Структура JWT токена
JWT состоит из трёх частей, разделённых точкой:
header.payload.signature
Каждая часть кодируется в Base64URL — это не шифрование, а просто кодирование. Любой может декодировать header и payload без ключа.
Header
Заголовок содержит тип токена и алгоритм подписи:
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Полезная нагрузка содержит claims — утверждения о пользователе:
{
"sub": "42",
"name": "Иван Иванов",
"role": "admin",
"iat": 1700000000,
"exp": 1700086400
}
Стандартные claims:
sub— идентификатор субъекта (обычно userId)iat— время выпуска токена (issued at)exp— время истечения (expiration)iss— издатель токена (issuer)
Signature
Подпись создаётся из закодированного заголовка, payload и секретного ключа:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
Подпись гарантирует целостность токена — если кто-то изменит payload, подпись не совпадёт и сервер отклонит токен.
Генерация и верификация JWT в Node.js
Самая популярная библиотека — jsonwebtoken:
npm install jsonwebtoken
Создание токена
const jwt = require('jsonwebtoken');
const SECRET_KEY = process.env.JWT_SECRET;
function generateToken(userId, role) {
return jwt.sign(
{
sub: userId, // идентификатор пользователя
role: role // роль для проверки прав доступа
},
SECRET_KEY,
{
expiresIn: '15m' // короткое время жизни — стандартная практика
}
);
}
const token = generateToken(42, 'admin');
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Проверка токена
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET_KEY);
return decoded; // возвращает payload, если токен валидный
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error('Токен истёк, необходимо обновить');
}
if (error instanceof jwt.JsonWebTokenError) {
throw new Error('Невалидный токен');
}
throw error;
}
}
Middleware для Express
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Токен не предоставлен' });
}
const token = authHeader.split(' ')[1]; // извлекаем токен из заголовка
try {
req.user = verifyToken(token); // добавляем данные пользователя в запрос
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
}
// Защищённый роут
app.get('/api/profile', authMiddleware, (req, res) => {
res.json({ userId: req.user.sub, role: req.user.role });
});
Access и Refresh токены
Если хранить данные в одном долгоживущем токене — это риск безопасности. Стандартное решение — два токена:
// Access токен — короткоживущий, используется для каждого запроса
const accessToken = jwt.sign(
{ sub: userId, role: user.role },
process.env.ACCESS_SECRET,
{ expiresIn: '15m' } // 15 минут — стандартный срок
);
// Refresh токен — долгоживущий, хранится в базе данных
const refreshToken = jwt.sign(
{ sub: userId },
process.env.REFRESH_SECRET,
{ expiresIn: '30d' } // 30 дней
);
Когда access токен истекает, клиент отправляет refresh токен на специальный эндпоинт и получает новую пару токенов — без повторного ввода пароля. Refresh токен хранится в БД, что позволяет его инвалидировать при выходе пользователя.
Частые ошибки
1. Секретный ключ прямо в коде
// Плохо — ключ попадёт в git-историю
const SECRET = 'supersecret123';
// Хорошо — ключ в переменных окружения
const SECRET = process.env.JWT_SECRET;
2. Слишком долгое время жизни access токена
Access токен с expiresIn: '365d' — серьёзная уязвимость. При утечке злоумышленник получит доступ на год. Используйте 15–60 минут для access и refresh токен для продления сессии.
3. Чувствительные данные в payload
// Плохо — payload декодируется без ключа
jwt.sign({ sub: userId, password: hash, cardNumber: '4111...' }, secret);
// Хорошо — только необходимый минимум
jwt.sign({ sub: userId, role: 'user' }, secret);
4. Игнорирование типов ошибок верификации
// Плохо — все ошибки обрабатываются одинаково
try {
jwt.verify(token, secret);
} catch {
return res.status(401).json({ error: 'Ошибка' });
}
// Хорошо — клиент знает, что именно произошло
try {
jwt.verify(token, secret);
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ code: 'TOKEN_EXPIRED' }); // клиент обновит токен
}
return res.status(401).json({ code: 'INVALID_TOKEN' }); // клиент редиректит на логин
}
Заключение
JWT — это удобный стандарт для stateless аутентификации. Вся информация хранится в самом токене, что снимает нагрузку с сервера и упрощает горизонтальное масштабирование.
Ключевые принципы работы с JWT:
- Храните секретные ключи только в переменных окружения
- Используйте короткое время жизни access токена (15–60 минут)
- Не помещайте чувствительные данные в payload — он не шифруется
- Всегда различайте типы ошибок при верификации
- Внедряйте схему access + refresh токенов для продакшн-приложений
Для большинства проектов этих правил достаточно, чтобы построить надёжную и безопасную систему аутентификации.






Комментарии
0