Защита API: JWT vs OAuth2 - выбираем правильно
В современной веб-разработке безопасность API является критически важной. Два наиболее популярных механизма — JWT и OAuth2 — часто вызывают путаницу. В этой статье мы разберем когда использовать каждый из них, как их комбинировать и какие подводные камни избегать.
Основные понятия: Аутентификация vs Авторизация
Аутентификация (Authentication) - процесс проверки подлинности пользователя. "Кто вы?"
Авторизация (Authorization) - процесс проверки прав доступа. "Что вам разрешено делать?"
JWT (JSON Web Token)
JWT — это стандарт для создания токенов доступа, основанный на JSON. Токен содержит полезную нагрузку (payload) с данными пользователя и подписан цифровой подписью.
Структура JWT
header.payload.signature
// Пример декодированного JWT:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622,
"roles": ["user", "admin"]
}
Типичный flow аутентификации с JWT
// 1. Клиент отправляет credentials
POST /api/auth/login
{
"username": "user",
"password": "pass"
}
// 2. Сервер проверяет и возвращает JWT
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}
// 3. Клиент использует токен в заголовках
GET /api/protected
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Преимущества JWT
- Stateless - не требует хранения состояния на сервере
- Самостоятельный - содержит всю необходимую информацию
- Масштабируемость - легко работать в распределенных системах
- Гибкость - можно хранить любые данные в payload
Недостатки JWT
- Невозможность отзыва - токен валиден до истечения срока
- Размер - токены больше чем session IDs
- Безопасность - при компрометации токена доступ открыт до expire
OAuth 2.0
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к пользовательским данным без раскрытия паролей.
Ключевые роли в OAuth2
- Resource Owner - пользователь
- Client - приложение, запрашивающее доступ
- Resource Server - API, хранящее защищенные данные
- Authorization Server - сервер, выдающий токены доступа
Authorization Code Flow (наиболее безопасный)
// 1. Редирект на страницу авторизации
https://auth-server.com/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read:user&
state=RANDOM_STRING
// 2. Пользователь авторизуется и получает code
// 3. Обмен code на access token
POST /oauth/token
{
"grant_type": "authorization_code",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "CALLBACK_URL"
}
// 4. Получение access token
{
"access_token": "ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN"
}
Когда использовать OAuth2
- Сторонние приложения, требующие доступ к API
- Единый вход (Single Sign-On)
- Делегирование прав доступа
- Интеграции между сервисами
Сравнительная таблица: JWT vs OAuth2
| Критерий | JWT | OAuth2 |
|---|---|---|
| Тип | Стандарт для токенов | Фреймворк авторизации |
| Основная цель | Аутентификация | Делегированная авторизация |
| Состояние | Stateless | Stateful (требует хранилище) |
| Сложность | Простая реализация | Сложная настройка |
| Использование | Внутренние API | Публичные API, сторонние приложения |
| Отзыв токенов | Сложно (через blacklist) | Легко (через revocation endpoint) |
Комбинирование JWT и OAuth2
Часто лучшим решением является комбинация обоих подходов. OAuth2 для потока авторизации и JWT для токенов доступа.
Гибридная архитектура
// Authorization Server выдает JWT токены
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN"
}
// Resource Server проверяет JWT без обращения к Authorization Server
function verifyToken(token) {
try {
const decoded = jwt.verify(token, PUBLIC_KEY);
return decoded; // { sub: "user123", scope: ["read", "write"] }
} catch (error) {
throw new Error('Invalid token');
}
}
Security Best Practices
Для JWT
- Используйте сильные алгоритмы - RS256 вместо HS256 для публичных API
- Устанавливайте короткое время жизни - 15-60 минут для access tokens
- Используйте refresh tokens - с более длительным сроком
- Валидируйте подпись - никогда не доверяйте неподписанным токенам
- Храните секреты безопасно - используйте environment variables
// Хорошая практика - короткоживущие access tokens
const accessToken = jwt.sign(
{ userId: user.id, roles: user.roles },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);
Для OAuth2
- Используйте Authorization Code Flow with PKCE - для мобильных и SPA приложений
- Валидируйте redirect_uri - предотвращайте открытые редиректы
- Используйте state параметр - защита от CSRF атак
- Ограничивайте scope - принцип минимальных привилегий
- Реализуйте token revocation - возможность отзыва токенов
Common Pitfalls и как их избежать
1. Хранение чувствительных данных в JWT
// Плохо
{
"userId": 123,
"password": "hashed_password", // Не хранить!
"creditCard": "1234-5678-9012-3456" // Опасно!
}
// Хорошо
{
"sub": "user123",
"roles": ["user"],
"permissions": ["read:profile"]
}
2. Слишком длительное время жизни токенов
// Плохо
{ expiresIn: '30d' } // 30 дней - слишком долго
// Хорошо
{ expiresIn: '15m' } // 15 минут + refresh token
3. Неправильная валидация audience
// Хорошая практика - проверять audience
jwt.verify(token, secret, {
audience: 'https://api.myapp.com',
issuer: 'https://auth.myapp.com'
});
4. Отсутствие защиты от replay атак
// Используйте jti (JWT ID) и храните использованные токены
{
"jti": "unique-token-id",
"iat": 1516239022,
"exp": 1516242622
}
Практические сценарии выбора
Сценарий 1: Монолитное веб-приложение
Рекомендация: Сессии или простые JWT
Используйте httpOnly cookies с короткоживущими JWT для stateless аутентификации.
Сценарий 2: Мобильное приложение + API
Рекомендация: JWT с refresh tokens
Access token: 15-30 минут, refresh token: 7-30 дней с возможностью отзыва.
Сценарий 3: Публичное API для сторонних разработчиков
Рекомендация: OAuth2 с JWT токенами
Authorization Code Flow with PKCE для безопасной авторизации.
Сценарий 4: Микросервисная архитектура
Рекомендация: JWT для межсервисной коммуникации
Каждый сервис может самостоятельно проверять JWT без центрального сервера.
Реализация middleware для Express.js
// JWT middleware
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')[1];
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.sendStatus(403);
}
req.user = user;
next();
});
} else {
res.sendStatus(401);
}
};
// RBAC middleware
const requireRole = (role) => {
return (req, res, next) => {
if (!req.user || !req.user.roles.includes(role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Использование
app.get('/api/admin', authenticateJWT, requireRole('admin'), (req, res) => {
res.json({ message: 'Admin access granted' });
});
Заключение
Используйте JWT когда:
- Нужна stateless аутентификация
- Работаете с внутренними API
- Требуется высокая производительность
- Микросервисная архитектура
Используйте OAuth2 когда:
- Делегируете доступ сторонним приложениям
- Реализуете Single Sign-On
- Работаете с публичными API
- Требуется тонкий контроль разрешений
Комбинируйте когда:
- Нужны преимущества обоих подходов
- OAuth2 для авторизации + JWT для токенов
- Требуется баланс безопасности и производительности
Правильный выбор механизма безопасности зависит от конкретных требований вашего приложения. Начинайте с простых решений и усложняйте по мере необходимости, всегда следуя security best practices.