Загрузка...
Загрузка...
Практическое руководство по реализации аутентификации с JWT: выдача токенов, проверка на сервере, хранение на клиенте и обновление сессии.
Подробное руководство по JWT: структура токена, заголовок, payload, подпись, алгоритмы и рекомендации по безопасному использованию.
БезопасностьСправочник HTTP-заголовков: запрос и ответ, кэширование, безопасность, CORS. Таблицы, примеры, рекомендации по настройке.
РазработкаРуководство по работе с JSON в REST API: парсинг ответов, валидация структуры, обработка ошибок и рекомендации для фронтенда и бэкенда.
РазработкаРуководство по типичным ошибкам в JSON: синтаксис, валидация, инструменты поиска и исправления некорректных данных.
Поделитесь с коллегами или изучите другие материалы блога
JWT широко используется для аутентификации в REST API и SPA. В этой статье рассмотрена пошаговая реализация: логин, выдача токенов, middleware проверки и хранение на клиенте.
const jwt = require('jsonwebtoken');
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await findUserByEmail(email);
if (!user || !await verifyPassword(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token, expiresIn: 3600 });
});
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid token' });
}
const token = authHeader.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (e) {
if (e.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
}
app.get('/api/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
// После логина
localStorage.setItem('token', data.token);
// При запросах
const token = localStorage.getItem('token');
fetch('/api/profile', {
headers: { Authorization: `Bearer ${token}` }
});
Минус: уязвимость к XSS. Если скрипт получает доступ к странице, он может прочитать токен.
Сервер устанавливает cookie при логине:
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000
});
Браузер автоматически отправляет cookie с запросами. JavaScript не имеет доступа к cookie — защита от XSS. Требуется настройка CORS и credentials.
Access token в памяти (переменная), refresh token в httpOnly cookie. При перезагрузке страницы — запрос нового access token через refresh.
app.post('/api/refresh', async (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
return res.status(401).json({ error: 'No refresh token' });
}
try {
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await findUserById(payload.sub);
if (!user) return res.status(401).json({ error: 'User not found' });
const accessToken = jwt.sign(
{ sub: user.id },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ token: accessToken });
} catch (e) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
JWT Decoder на rechecker.ru позволяет декодировать токен и просмотреть payload. Используйте только с тестовыми токенами.
Перехватывайте 401 и перенаправляйте на логин или обновляйте токен:
async function fetchWithAuth(url, options = {}) {
let token = getToken();
let response = await fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${token}` }
});
if (response.status === 401) {
const refreshed = await refreshToken();
if (refreshed) {
token = getToken();
response = await fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${token}` }
});
}
}
return response;
}
При localStorage — удалить токен. При cookie — сервер должен очистить cookie в ответе на запрос логаута. Для refresh token — добавить в blacklist или удалить из хранилища на сервере.
Структура JWT, алгоритмы и основы безопасности — в JWT токены: как работают, структура и безопасность.
При хранении токена в cookie и запросах с другого домена настройте CORS:
app.use(cors({
origin: 'https://frontend.example.com',
credentials: true
}));
Клиент должен отправлять credentials: 'include' в fetch.
После верификации токена проверяйте claims:
function requireRole(role) {
return (req, res, next) => {
if (req.user?.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
app.get('/api/admin', authMiddleware, requireRole('admin'), handler);
Ограничьте количество попыток входа с одного IP для защиты от брутфорса. Используйте Redis или in-memory store для счётчиков.
Логируйте успешные и неуспешные попытки аутентификации (без паролей и токенов). Это помогает при расследовании инцидентов.
Для тестов используйте тестовые токены с коротким временем жизни. Мокайте middleware аутентификации в unit-тестах. В E2E-тестах получайте токен через тестовый логин или фикстуру.
При нескольких приложениях (issuers) используйте iss и aud для проверки. Храните публичные ключи или секреты по issuer. При верификации проверяйте, что iss соответствует ожидаемому издателю.
Пошагово: добавьте выдачу JWT при логине параллельно с сессией. Клиенты переключаются на JWT. После полного перехода отключите сессии. Убедитесь, что refresh flow и логаут работают корректно.
Refresh token должен храниться безопаснее access token: httpOnly cookie, защищённое хранилище. При компрометации — немедленная инвалидация. Рассмотрите привязку к устройству или fingerprint для дополнительной защиты.
Логируйте: успешный логин, неудачный логин (с IP, без пароля), истечение токена, refresh. Это необходимо для аудита и расследования инцидентов. Не логируйте сами токены и пароли.