JWT токены в TypeScript: полное руководство с библиотекой jsonwebtoken
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанных на формате JSON. JWT широко используется для аутентификации и обмена информацией между клиентом и сервером. В TypeScript, благодаря строгой типизации, работа с JWT становится особенно удобной и безопасной. В этой статье мы рассмотрим, как использовать популярную библиотеку jsonwebtoken в TypeScript для создания, проверки и управления JWT токенами.
Установка и настройка
Для начала работы установим необходимые пакеты:
npm install jsonwebtoken
npm install -D @types/jsonwebtoken
Первый пакет — сама библиотека, второй — типы TypeScript для неё. После установки создадим файл jwt.service.ts, где будем хранить всю логику работы с токенами.
Создание JWT токена
Основная функция jsonwebtoken — sign(). Рассмотрим её использование с типизацией:
import jwt from 'jsonwebtoken';
interface TokenPayload {
userId: number;
email: string;
role: 'user' | 'admin';
}
const createAccessToken = (payload: TokenPayload): string => {
const secret = process.env.JWT_SECRET || 'default_secret';
const options: jwt.SignOptions = {
expiresIn: '15m', // токен действителен 15 минут
algorithm: 'HS256',
};
return jwt.sign(payload, secret, options);
};
Важно: никогда не храните секретный ключ в коде. Используйте переменные окружения (.env файл).
Верификация и декодирование токена
Для проверки подлинности токена используется функция verify(). Она автоматически проверяет подпись и срок действия:
const verifyToken = (token: string): TokenPayload => {
const secret = process.env.JWT_SECRET || 'default_secret';
try {
const decoded = jwt.verify(token, secret) as TokenPayload;
return decoded;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error('Токен истёк');
}
if (error instanceof jwt.JsonWebTokenError) {
throw new Error('Неверный токен');
}
throw error;
}
};
Обратите внимание на приведение типа as TokenPayload — это гарантирует, что TypeScript будет знать структуру раскодированных данных.
Refresh-токены: продление сессии
На практике access-токены живут недолго (15-30 минут), поэтому нужен механизм их обновления. Для этого используется refresh-токен с более длительным сроком жизни:
const createRefreshToken = (payload: TokenPayload): string => {
const secret = process.env.JWT_REFRESH_SECRET || 'refresh_secret';
const options: jwt.SignOptions = {
expiresIn: '7d', // неделя
algorithm: 'HS256',
};
return jwt.sign(payload, secret, options);
};
const refreshAccessToken = (refreshToken: string): string => {
const secret = process.env.JWT_REFRESH_SECRET || 'refresh_secret';
try {
const decoded = jwt.verify(refreshToken, secret) as TokenPayload;
// Можно проверить, не отозван ли refresh-токен в базе данных
return createAccessToken({
userId: decoded.userId,
email: decoded.email,
role: decoded.role,
});
} catch {
throw new Error('Недействительный refresh-токен');
}
};
Middleware для Express в TypeScript
Интеграция JWT с Express.js требует создания middleware. Вот типизированная версия:
import { Request, Response, NextFunction } from 'express';
interface AuthRequest extends Request {
user?: TokenPayload;
}
const authMiddleware = (req: AuthRequest, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Токен не предоставлен' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Неавторизованный доступ' });
}
};
Теперь в защищённых маршрутах можно использовать req.user:
app.get('/profile', authMiddleware, (req: AuthRequest, res) => {
res.json({ user: req.user });
});
Лучшие практики безопасности
- Храните секреты в .env — используйте
dotenvили встроенные механизмы Node.js. - Используйте разные секреты для access и refresh токенов.
- Устанавливайте разумные сроки жизни: access — 15-30 минут,