Основы аутентификации и авторизации

Безопасность 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
  1. Header - тип токена и алгоритм
{
  "alg": "HS256",
  "typ": "JWT"
}
  1. Payload - данные
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622
}
  1. 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

  1. Используйте короткое время жизни
const token = jwt.sign(payload, secret, {
  expiresIn: '15m' // 15 минут
});
  1. Не храните sensitive данные в payload
// Плохо
const payload = {
  userId: 123,
  password: 'secret123',  // Никогда!
  creditCard: '4111111111111111'  // Никогда!
};

// Хорошо
const payload = {
  userId: 123,
  role: 'user',
  permissions: ['read', 'write']
};
  1. Используйте 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

  1. Resource Owner - пользователь
  2. Client - приложение, запрашивающее доступ
  3. Authorization Server - сервер, выдающий токены
  4. 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.

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.