Аутентификация через JWT
JWT — самодостаточный токен: сервер выдаёт его при входе, клиент предъявляет в каждом запросе.
«JWT — это пропуск с печатью. Подделать содержимое нельзя — печать (подпись) не сойдётся.»
HTTP не помнит, кто ты: каждый запрос независим. Чтобы сервер узнавал авторизованного пользователя, после входа ему выдают токен, который он прикладывает к последующим запросам. Самый распространённый формат — JWT (JSON Web Token). В этом уроке разберём его устройство и напишем middleware-защиту.
Из чего состоит JWT
JWT — это строка из трёх частей, разделённых точками: заголовок, полезная нагрузка и подпись. Первые две — это закодированный в base64 JSON, третья — криптографическая подпись:
header.payload.signature
eyJhbGciOiJIUzI1NiJ9 . eyJpZCI6MSwicm9sZSI6InVzZXIifQ . SflKxw...
| | |
{alg:HS256} {id:1, role:user} подпись секретом
(base64) (base64) (нельзя подделать)Важно понять: payload не зашифрован, а лишь закодирован — любой может его прочитать. Секрет защищает не от чтения, а от подделки: без него нельзя сделать подпись для изменённого payload.
Поток аутентификации
1. POST /login (email+пароль)
|
v
сервер проверил пароль -> выдал JWT
|
v
2. GET /profile
Authorization: Bearer <JWT>
|
v
middleware проверил подпись -> req.user = {...} -> доступВыдача и проверка токена
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
// при входе -- выдаём токен на 15 минут
const token = jwt.sign({ id: user.id, role: user.role }, SECRET, {
expiresIn: '15m'
});
// middleware-защита: проверяет токен из заголовка
function auth(req, res, next) {
const header = req.get('Authorization') || '';
const token = header.replace('Bearer ', '');
try {
req.user = jwt.verify(token, SECRET); // упадёт, если подпись неверна
next();
} catch (e) {
res.status(401).json({ error: 'нужна авторизация' });
}
}
app.get('/profile', auth, (req, res) => res.json(req.user));Декодируем payload в браузере
Разберём структуру JWT руками: расшифруем base64-части и достанем данные. Так видно, что payload открыт для чтения (но защищён от подделки):
// собираем учебный токен (без настоящей подписи)
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const payload = btoa(JSON.stringify({ id: 1, role: 'admin' }));
const token = header + '.' + payload + '.signature';
console.log('Токен:', token);
// клиент (или кто угодно) может прочитать payload:
const parts = token.split('.');
const decoded = JSON.parse(atob(parts[1]));
console.log('Внутри:', decoded); // { id: 1, role: 'admin' }
// но изменить и переподписать без секрета -- нельзяКак работает под капотом
Подпись считается так: сервер берёт header.payload, прогоняет через алгоритм (например HS256) с секретным ключом и получает подпись. При проверке он повторяет вычисление и сравнивает результат с подписью из токена. Если кто-то изменил payload, подпись не совпадёт — токен отвергается. Секрет известен только серверу, поэтому подделать подпись нельзя.
Частые ошибки
- Класть секреты в payload. Он читается всеми. Никаких паролей и приватных данных.
- Вечные токены. Задавай
expiresIn; для долгих сессий используй пару access+refresh. - Слабый или захардкоженный секрет. Минимум 32 случайных символа из переменной окружения.
Best practices
- Короткий access-токен (15 мин) плюс долгий refresh-токен в базе для отзыва.
- Храни секрет в
process.env.JWT_SECRET, не в коде. - Проверяй токен в middleware и клади пользователя в
req.userдля маршрутов.
Итоги
JWT — самодостаточный подписанный пропуск: сервер выдаёт его при входе, клиент предъявляет в заголовке, middleware проверяет подпись. Payload открыт для чтения, но защищён от подделки секретом. Теперь у нас есть полноценная авторизация; в следующем разделе займёмся безопасностью всего приложения и его выкаткой в прод.
Access и refresh: зачем два токена
У JWT есть встроенный недостаток: его нельзя "отозвать" до истечения срока, ведь сервер не хранит выданные токены. Решение — пара токенов. Короткоживущий access-токен (15 минут) ходит в каждом запросе; если он утечёт, окно злоупотребления невелико. Долгоживущий refresh-токен (дни) хранится в базе и обменивается на новый access, когда тот истёк. Поскольку refresh лежит в базе, его можно пометить отозванным при выходе, смене пароля или подозрении на взлом — и пользователь мгновенно теряет доступ. Эта схема сочетает удобство самодостаточного JWT с возможностью немедленного отзыва, поэтому именно её используют в большинстве серьёзных приложений.