JWT-авторизация в Express: токены на практике
Содержание
О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».
Авторизация — «кто ты и что тебе можно» — есть почти в каждом приложении. JWT-токены стали популярным способом её реализовать в Express. Разберём механизм и, что важнее, его безопасность.
Что такое JWT
JWT (JSON Web Token) — подписанный токен, который сервер выдаёт после успешного входа. Дальше клиент присылает его с каждым запросом, и сервер по подписи убеждается, что токен настоящий, — не спрашивая пароль снова.
Токен состоит из трёх частей через точку:
eyJhbGciOi... . eyJ1c2VySWQiOjQyfQ . SflKxwRJSM...
заголовок данные (payload) подпись
Заголовок + данные
Просто закодированы, НЕ зашифрованы
- Заголовок: алгоритм подписи
- Данные: userId, роль, срок
- Кодировка base64 — читается любым!
- НЕ кладите сюда секреты
Подпись
Защищает от подделки
- Хеш от заголовка+данных с секретом
- Проверяется секретом на сервере
- Подделать без секрета нельзя
- Гарантирует подлинность, не секретность
Критично понимать: данные в JWT НЕ зашифрованы, а лишь подписаны. Любой, у кого есть токен, прочитает его содержимое (это просто base64). Подпись гарантирует, что токен не подделан, но не что его не прочитают. Отсюда правило: в токен не кладут пароли и чувствительные данные — только идентификатор и роль.
Вход и выдача токена
const jwt = require('jsonwebtoken');
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findByEmail(email);
// проверяем пароль (хеш сравнивают через bcrypt — см. crypto)
if (!user || !await verifyPassword(password, user.passwordHash)) {
return res.status(401).json({ error: 'Неверные данные' });
}
// выдаём токен
const token = jwt.sign(
{ userId: user.id, role: user.role }, // данные (payload)
process.env.JWT_SECRET, // секрет — из окружения!
{ expiresIn: '1h' }, // срок истечения — обязательно
);
res.json({ token });
});
После проверки пароля сервер jwt.sign создаёт подписанный токен с данными пользователя, секретом и сроком. Секрет берут из переменных окружения, никогда не из кода. Пароли в БД хранят хешированными (про хеширование — в статье о crypto), а не в открытом виде — сравнивают хеши.
Middleware проверки токена
function authenticate(req, res, next) {
const header = req.headers.authorization; // 'Bearer <token>'
const token = header && header.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Нужна авторизация' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET); // проверка подписи
req.user = payload; // кладём пользователя в req для обработчиков
next();
} catch (err) {
return res.status(401).json({ error: 'Неверный или истёкший токен' });
}
}
// защищаем маршрут
app.get('/profile', authenticate, (req, res) => {
res.json({ userId: req.user.userId }); // req.user из middleware
});
Проверка токена — классическое middleware: читает токен из заголовка Authorization, проверяет подпись через jwt.verify и кладёт данные пользователя в req.user. Защищённые маршруты подключают это middleware перед обработчиком — и внутри уже есть req.user. verify бросает на неверной или истёкшей подписи — это ловят и отвечают 401.
JWT против сессий
Стоит понимать альтернативу:
- сессии — состояние на сервере, клиент держит лишь ID сессии. Сервер помнит, кто вошёл; легко отозвать доступ, но нужно хранилище сессий;
- JWT — самодостаточен, сервер ничего не хранит, только проверяет подпись. Проще масштабировать (любой воркер cluster проверит токен), но отозвать токен до истечения срока сложно — сервер его не отслеживает.
Отсюда практика: JWT делают короткоживущими (expiresIn: '15m') плюс отдельный refresh-токен для обновления. Это ограничивает ущерб при утечке токена.
Где хранить токен на клиенте
Больной вопрос безопасности:
httpOnly-cookie — недоступна JavaScript, поэтому защищена от кражи при XSS. Безопаснее, но требует защиты от CSRF;- localStorage — удобнее (легко читать и слать в заголовке), но уязвим: любой вредоносный скрипт на странице прочитает токен. От этого предостерегает и статья про localStorage.
Выбор зависит от модели угроз, но для чувствительных приложений безопаснее httpOnly-cookie. Класть токены авторизации в localStorage — распространённая, но рискованная практика.
Частые ошибки безопасности
- секрет подписи в коде — только в переменных окружения, иначе утечёт с репозиторием, и любой подделает токены;
- токен без срока (
expiresIn) — украденный токен действует вечно; - чувствительные данные в payload — JWT не зашифрован, пароль или карта внутри = утечка;
- токен в localStorage для чувствительных приложений — уязвим к XSS;
- отсутствие проверки прав — токен подтверждает «кто ты» (аутентификация), но «что тебе можно» (авторизацию) проверяют отдельно: наличие токена ≠ право удалять чужие данные.
Смежные темы: middleware — проверка токена; crypto и хеширование паролей; localStorage и XSS — где хранить; переменные окружения — секрет подписи; валидация. Полный список — в уроках Node.js.