Middleware в Express.js для проверки токенов
В HTTP-приложениях на базе Express.js проверка доступа обычно реализуется через промежуточные функции, которые перехватывают запрос до выполнения бизнес-логики маршрута. В контексте токенов основная задача такого слоя сводится к нескольким операциям:
Middleware становится точкой централизованного контроля безопасности, исключая дублирование логики в каждом роуте.
Стандартным способом передачи токена в HTTP является заголовок:
Authorization: Bearer <token>
В middleware первым шагом выполняется разбор строки:
Отсутствие заголовка или некорректный формат рассматриваются как ошибка авторизации без перехода к криптографической проверке.
JSON Web Token представляет собой компактный формат передачи данных между сторонами. Структура состоит из трёх частей:
Основная цель middleware — проверить signature и валидность payload,
включая срок действия (exp).
В современных Node.js приложениях предпочтение часто отдаётся библиотеке jose, которая реализует стандарты JOSE (JWS, JWE, JWT) без устаревших зависимостей.
Ключевые возможности:
Базовый метод проверки токена:
import { jwtVerify } from 'jose';
Типовая структура middleware в Express.js:
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
export async function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header) {
return res.status(401).json({ message: 'Отсутствует Authorization заголовок' });
}
const [type, token] = header.split(' ');
if (type !== 'Bearer' || !token) {
return res.status(401).json({ message: 'Некорректный формат токена' });
}
try {
const { payload } = await jwtVerify(token, secret);
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ message: 'Недействительный токен' });
}
}
В результате успешной проверки объект payload становится
доступным через req.user, что позволяет использовать данные
пользователя в последующих middleware и контроллерах.
При использовании HMAC (HS256) один и тот же секрет применяется для подписи и проверки токена.
const secret = new TextEncoder().encode('super-secret-key');
const { payload } = await jwtVerify(token, secret, {
algorithms: ['HS256']
});
Особенности:
При использовании RSA модель меняется: приватный ключ подписывает токен, публичный — проверяет.
import { jwtVerify, importSPKI } from 'jose';
const publicKey = await importSPKI(process.env.PUBLIC_KEY, 'RS256');
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ['RS256']
});
Преимущества:
В распределённых системах ключи часто ротируются, поэтому используется JWKS endpoint.
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://auth.example.com/.well-known/jwks.json')
);
export async function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header) {
return res.status(401).end();
}
const token = header.split(' ')[1];
try {
const { payload } = await jwtVerify(token, JWKS);
req.user = payload;
next();
} catch {
return res.status(401).end();
}
}
JWKS обеспечивает:
После успешной верификации middleware обычно добавляет объект пользователя:
req.user = {
id: payload.sub,
email: payload.email,
roles: payload.roles
};
Это позволяет downstream-слою не выполнять повторную проверку токена.
Middleware часто расширяется логикой авторизации:
export function requireRole(role) {
return (req, res, next) => {
if (!req.user?.roles?.includes(role)) {
return res.status(403).json({ message: 'Доступ запрещён' });
}
next();
};
}
Использование в маршрутах:
app.get('/admin', authMiddleware, requireRole('admin'), handler);
JWT содержит поле exp, которое автоматически проверяется
библиотекой jose. При истечении срока действия выбрасывается ошибка,
которая должна приводить к ответу:
Типизация ошибок позволяет отделять просроченные токены от поддельных или повреждённых.
При проектировании middleware необходимо учитывать ряд факторов:
algorithmsiss (issuer) и aud (audience)
снижает риск подделки токеновawait jwtVerify(token, JWKS, {
issuer: 'https://auth.example.com',
audience: 'api.service.local'
});
Эти параметры добавляют дополнительный уровень контроля над тем, кем и для кого был выпущен токен.
В реальных приложениях middleware обычно выносится в отдельный слой и подключается глобально или выборочно:
app.use('/api', authMiddleware);
или точечно:
app.get('/profile', authMiddleware, getProfile);
Такая организация позволяет разделять публичные и защищённые маршруты без дублирования логики проверки.
При использовании JWKS важно учитывать:
Библиотека jose автоматически включает механизмы кэширования при
использовании createRemoteJWKSet, но в крупных системах
часто добавляется дополнительный слой кэша на уровне инфраструктуры.