Основы аутентификации и авторизации
Безопасность API - это критически важный аспект современной разработки. В этом уроке мы разберём основные концепции и методы защиты API.
Аутентификация vs Авторизация
Аутентификация - это процесс проверки, кто вы такой. Авторизация - это процесс проверки, что вам можно делать.
Пример из жизни:
- Когда вы показываете паспорт на входе в офис - это аутентификация
- Когда охранник проверяет, есть ли вы в списке посетителей - это авторизация
В контексте API:
# Аутентификация: кто делает запрос?
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# Авторизация: может ли этот пользователь удалять посты?
DELETE /api/posts/123
Методы аутентификации
1. Basic Authentication
Самый простой метод - передача логина и пароля в каждом запросе.
# Логин:пароль кодируются в Base64
curl -H "Authorization: Basic YWxpY2U6cGFzc3dvcmQxMjM=" \
https://api.example.com/users
Плюсы:
- Простота реализации
- Поддерживается везде
Минусы:
- Небезопасно без HTTPS
- Нужно хранить пароль на клиенте
- Нет возможности отозвать доступ
2. API Keys
Клиент получает уникальный ключ для доступа к API.
# В заголовке
curl -H "X-API-Key: abc123def456" \
https://api.example.com/users
# В query параметре
curl https://api.example.com/users?api_key=abc123def456
Плюсы:
- Простота использования
- Можно ограничить права ключа
- Легко отозвать
Минусы:
- Ключ = полный доступ
- Сложно ограничить по времени
- Нужно безопасно хранить
3. Bearer Tokens
Современный подход с использованием токенов.
# Получаем токен
curl -X POST https://api.example.com/auth/login \
-d '{"email": "user@example.com", "password": "secret"}'
# Используем токен
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://api.example.com/users
Плюсы:
- Можно задать время жизни
- Не передаём пароль в каждом запросе
- Поддержка refresh токенов
Минусы:
- Нужно обновлять токены
- Требует хранилища токенов
JWT (JSON Web Tokens)
JWT - это стандарт для создания токенов доступа.
Структура JWT
JWT состоит из трёх частей, разделённых точками:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Header - тип токена и алгоритм
{
"alg": "HS256",
"typ": "JWT"
}
- Payload - данные
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
- Signature - подпись для проверки
Пример работы с JWT
// Node.js с jsonwebtoken
const jwt = require('jsonwebtoken');
// Создание токена
const payload = {
userId: 123,
email: 'user@example.com',
role: 'user'
};
const token = jwt.sign(payload, 'secret-key', {
expiresIn: '1h'
});
// Проверка токена
try {
const decoded = jwt.verify(token, 'secret-key');
console.log(decoded);
} catch (err) {
console.error('Invalid token');
}
JWT Best Practices
- Используйте короткое время жизни
const token = jwt.sign(payload, secret, {
expiresIn: '15m' // 15 минут
});
- Не храните sensitive данные в payload
// Плохо
const payload = {
userId: 123,
password: 'secret123', // Никогда!
creditCard: '4111111111111111' // Никогда!
};
// Хорошо
const payload = {
userId: 123,
role: 'user',
permissions: ['read', 'write']
};
- Используйте refresh токены
// Access token - короткоживущий
const accessToken = jwt.sign(payload, secret, {
expiresIn: '15m'
});
// Refresh token - долгоживущий
const refreshToken = jwt.sign(
{ userId: payload.userId },
refreshSecret,
{ expiresIn: '30d' }
);
OAuth 2.0
OAuth 2.0 - это протокол авторизации, позволяющий приложениям получать ограниченный доступ к ресурсам пользователя.
Роли в OAuth 2.0
- Resource Owner - пользователь
- Client - приложение, запрашивающее доступ
- Authorization Server - сервер, выдающий токены
- Resource Server - API с защищёнными ресурсами
Authorization Code Flow
Самый безопасный flow для веб-приложений:
1. Пользователь нажимает "Войти через Google"
→ Redirect to: https://accounts.google.com/oauth/authorize?
client_id=abc123&
redirect_uri=https://myapp.com/callback&
response_type=code&
scope=email%20profile
2. Пользователь разрешает доступ
→ Google redirects to: https://myapp.com/callback?code=xyz789
3. Приложение обменивает код на токен
POST https://oauth2.googleapis.com/token
{
"code": "xyz789",
"client_id": "abc123",
"client_secret": "secret456",
"grant_type": "authorization_code"
}
4. Получаем access token
{
"access_token": "ya29.a0AfH6SMBx...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1//0gFu3..."
}
Scopes (области доступа)
Scopes ограничивают, к чему токен даёт доступ:
// Запрашиваем только email и профиль
const authUrl = `https://accounts.google.com/oauth/authorize?
client_id=${clientId}&
scope=email%20profile&
response_type=code`;
// Токен будет иметь доступ только к email и profile API
Безопасность API
1. Всегда используйте HTTPS
server {
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Redirect HTTP to HTTPS
if ($scheme != "https") {
return 301 https://$server_name$request_uri;
}
}
2. Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // максимум 100 запросов
message: 'Too many requests'
});
app.use('/api/', limiter);
3. CORS (Cross-Origin Resource Sharing)
const cors = require('cors');
app.use(cors({
origin: 'https://trusted-domain.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
4. Валидация входных данных
const { body, validationResult } = require('express-validator');
app.post('/api/users',
body('email').isEmail(),
body('password').isLength({ min: 8 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Обработка валидных данных
}
);
5. Безопасные заголовки
const helmet = require('helmet');
app.use(helmet());
// Или вручную
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
Хранение токенов на клиенте
В браузере - storage в JS
// LocalStorage - удобно, но уязвимо для XSS
localStorage.setItem('token', accessToken);
// SessionStorage - безопаснее, но теряется при закрытии вкладки
sessionStorage.setItem('token', accessToken);
// HttpOnly Cookie - самый безопасный вариант
// Устанавливается сервером:
res.cookie('token', accessToken, {
httpOnly: true, // Недоступен из JS
secure: true, // Только по HTTPS
sameSite: 'strict', // Защита от CSRF
maxAge: 3600000 // 1 час
});
В мобильных приложениях
// iOS - Keychain
let keychain = Keychain(service: "com.myapp")
keychain["access_token"] = token
// Android - SharedPreferences (encrypted)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
// ...
)
prefs.edit().putString("token", accessToken).apply()
Итоги
Безопасность API - это комплексная задача, требующая:
- Правильного выбора метода аутентификации
- Безопасного хранения и передачи токенов
- Защиты от распространённых атак
- Регулярного обновления и мониторинга
В следующем уроке мы изучим принципы проектирования REST API и лучшие практики создания понятных и удобных интерфейсов. Безопасность URL-параметров - в уроке web/url-security.