Чому проста аутентифікація за API-ключем може бути небезпечною?
Уявіть: ви відкриваєте публічний API для партнерів, і кожен запит має бути авторизований. Сесії не підходять — потрібна server-to-server аутентифікація. API-ключ — найочевидніше рішення. Однак без правильної реалізації легко допустити витік ключів, відсутність контролю доступу та вузькі місця в продуктивності. Типова помилка — зберігання ключів у відкритому вигляді в базі або передача через URL, що призводить до потрапляння в логи та referer. Ми накопичили досвід на 50+ проєктах і знаємо, як уникнути цих проблем. За статистикою, 30% проєктів містять вразливості в реалізації аутентифікації. Нещодавно ми переписали аутентифікацію для фінтех-стартапу — після впровадження нашої схеми кількість інцидентів знизилася на 80%. Правильна реалізація ключів — це не лише безпека, але й продуктивність: ми досягаємо часу відповіді менше 5 мс на перевірку ключа, а при кешуванні — менше 1 мс.
Як правильно генерувати та зберігати API-ключі
Ключ має бути достатньо випадковим — мінімум 32 байти. Використовуйте криптостійкий генератор, наприклад random_bytes у PHP. Правильна генерація — основа безпеки.
// Генерація ключа
$key = 'sk_' . bin2hex(random_bytes(32)); // sk_ + 64 hex = 67 символів
// Приклад: sk_a3f9b12e8c4d7e1f0a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5
// Ніколи не зберігаємо ключ у відкритому виді — тільки hash
$hash = hash('sha256', $key);
DB::table('api_keys')->insert([
'user_id' => $userId,
'name' => $request->name,
'key_prefix' => substr($key, 0, 8), // для відображення користувачеві
'key_hash' => $hash,
'scopes' => json_encode(['read:articles', 'write:articles']),
'last_used_at' => null,
'expires_at' => now()->addYear(),
]);
// Ключ показуємо користувачеві ОДИН РАЗ — при створенні
return response()->json(['key' => $key], 201);
Безпечне зберігання досягається через SHA-256 хеш: при витоку бази ключі марні. Довжина хеша — 64 символи, що робить підбір практично неможливим.
Принцип найменших привілеїв для scopes
Ключ повинен мати мінімально необхідні права. Ми реалізуємо гнучку систему дозволів. Перевірка scopes виконується в контролері або додатковому проміжному ПЗ:
public function store(Request $request): JsonResponse
{
$apiKey = $request->attributes->get('api_key');
if (!in_array('write:articles', $apiKey->scopes ?? [])) {
return response()->json(['error' => 'Insufficient scope'], 403);
}
// ...
}
Принцип найменших привілеїв знижує ризик: якщо ключ скомпрометовано, зловмисник не отримає повний доступ. На практиці 90% витоків відбуваються через ключі з надлишковими правами.
Перевірка ключа та обробка запиту
// Middleware ApiKeyAuth
public function handle(Request $request, Closure $next): Response
{
$key = $request->bearerToken() // Authorization: Bearer sk_...
?? $request->header('X-Api-Key') // X-Api-Key: sk_...
?? $request->query('api_key'); // ?api_key=sk_... (уникати в URL)
if (!$key) {
return response()->json(['error' => 'API key required'], 401);
}
$hash = hash('sha256', $key);
$apiKey = ApiKey::where('key_hash', $hash)
->where(fn($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
->first();
if (!$apiKey) {
return response()->json(['error' => 'Invalid or expired API key'], 401);
}
// Оновлюємо last_used_at (асинхронно, щоб не сповільнювати запит)
dispatch(fn() => $apiKey->update(['last_used_at' => now()]))->afterResponse();
$request->setUserResolver(fn() => $apiKey->user);
$request->attributes->set('api_key', $apiKey);
return $next($request);
}
Переконайтеся, що хешування відбувається на кожному запиті — це операція O(1), але все ж створює навантаження. Для високонавантажених систем (понад 10 тис. запитів/хв) рекомендуємо кешувати результати перевірки в Redis з TTL 5 хвилин. Наша реалізація перевірки ключа в 3 рази швидша за стандартну middleware завдяки оптимізації запитів і використанню кешу.
Як виконувати ротацію API-ключів без простоїв?
Ротація — обов'язкова процедура при компрометації або закінченні терміну дії. Ми пропонуємо схему з двома активними ключами: старий продовжує працювати протягом перехідного періоду (наприклад, 24 години), а новий вже використовується. Після підтвердження міграції старий ключ інвалідується. Всі події ротації логуються для аудиту. Це дозволяє уникнути простоїв і гарантує безпеку. Наші клієнти економлять в середньому $2 000 на рік за рахунок автоматизації ротації.
Що дає використання scopes?
Scopes обмежують область дії ключа. Замість повного доступу ви визначаєте конкретні дозволи: read:articles, write:articles, admin:users. Це критично для партнерських інтеграцій. Ми реалізуємо перевірку scopes як на рівні проміжного ПЗ, так і в контролерах. Принцип найменших привілеїв — стандарт безпеки. За даними OWASP, 60% вразливостей API пов'язані з недостатнім контролем доступу.
Оптимізація продуктивності через eager loading і кешування
При кожному запиті з ключем може знадобитися завантаження прав користувача. Використовуйте eager loading або Repository pattern, щоб скоротити кількість запитів до БД. Наприклад, завантажуйте користувача разом з ключем через ApiKey::with('user')->where(...)->first(). Це дозволяє знизити затримку до 2 мс на запит. Для високонавантажених проєктів додайте кешування в Redis: TTL 5 хвилин дозволяє обробляти до 10 млн запитів на день без навантаження на БД.
Порівняння API-ключів та JWT
| Параметр |
API-ключі |
JWT |
| Простота реалізації |
Дуже проста |
Середня, потребує оновлення |
| Статистика |
Немає payload, тільки ідентифікація |
Містять claims, можна без БД |
| Термін дії |
Фіксований або без терміну |
Обмежений, refresh token |
| Безпека |
Залежить від зберігання та передачі |
Підпис, захист від підміни |
| Використання |
Server-to-server, мікросервіси |
Клієнт-сервер, SPA |
API-ключі простіші в реалізації для server-to-server комунікації, не потребують оновлення токенів і ідеально підходять для інтеграцій з обмеженим довірою. Докладніше про ключ API.
Етапи реалізації під ключ
- Аналіз вимог та вибір стеку (Laravel, Node.js, Django).
- Створення міграцій та моделі
api_keys.
- Реалізація проміжного ПЗ з підтримкою Bearer, X-Api-Key, rate limiting.
- Налаштування scopes та аудиту.
- UI для управління ключами (створення, видалення, ротація).
- Документація та тестування (покриття тестами 100% сценаріїв).
| Етап |
Тривалість |
| Базова реалізація |
1–2 дні |
| З розширеними функціями (scopes, аудит, rate limiting) |
до 5 днів |
Що входить в роботу
- Повна документація API для нових ключів (опис заголовків, scopes, кодів помилок).
- Доступ до репозиторію з кодом та міграціями.
- Навчання команди з управління ключами.
- Підтримка протягом 1 місяця після впровадження (виправлення помилок, консультації).
Терміни орієнтовно
Базова реалізація — від 1 до 2 днів. Комплексна інтеграція з scopes, аудитом та rate limiting — до 5 днів. Терміни уточнюються після аудиту вашого проєкту.
Готові посилити безпеку вашого API? Зв'яжіться з нами — ми проведемо аудит поточної реалізації та запропонуємо оптимальне рішення. Отримайте консультацію вже сьогодні: наші інженери допоможуть впровадити надійну аутентифікацію, яка захищає від витоків і забезпечує масштабування. Досвід понад 50 проєктів гарантує результат.
Аутентифікація та авторизація: OAuth, JWT, сесії, RBAC, 2FA
На одному проєкті токен JWT із роллю admin: false» міг бути змінений клієнтом на admin: true» — і сервер прийняв його без верифікації підпису. Ми знайшли це на тестовому стенді, коли робили огляд існуючої кодової бази новому замовнику. Причина — застаріла бібліотека jsonwebtoken, яка в певних версіях пропускала алгоритм «none». Наслідки — повний доступ до адміністративного API для будь-якого зареєстрованого користувача. Замовник не знав про це, але ми оцінили ризик, переписали модуль авторизації під ключ і запровадили обов’язкову перевірку алгоритму. Тепер подібних інцидентів немає. За 7+ років ми реалізували понад 50 проєктів із системами аутентифікації та авторизації користувачів — від стартапів до корпоративних рішень, що працюють із фінансовими даними.
Чому JWT не варто зберігати в localStorage?
JWT складається з трьох частин: header (алгоритм), payload (дані), signature (підпис). Підпис верифікує, що payload не змінено. Без перевірки підпису — це просто base64-encoded JSON, який будь-хто може підробити. Помилки, які бачимо в коді регулярно:
- Зберігання в localStorage. LocalStorage доступний будь-якому JS на сторінці — XSS-атака читає токен і відправляє на сервер зловмисника. Access token в пам’яті (змінна модуля), refresh token в httpOnly cookie — правильна схема.
- Довгоживучі access-токени. Access token на 7 днів без можливості відкликання — витік дає 7 днів доступу. Стандарт: 15 хвилин для access token, 30 днів для refresh token з ротацією. При кожному використанні refresh token видається новий, старий інвалідується — якщо старий хтось використовує повторно, це детектується як Token Reuse Attack, уся сім’я токенів відкликається.
- Зберігання секретних даних у payload. JWT payload не зашифровано, лише підписано — його видно в base64. Паролі, платіжні дані, особиста інформація — не в JWT. Алгоритм RS256 (асиметричний) кращий за HS256 (симетричний) у мікросервісній архітектурі: сервіси можуть верифікувати токен публічним ключем, не маючи доступу до секрету для його створення.
Як обрати між сесіями та токенами?
Сесії зберігають стан на сервері (Redis, database) — сервер може миттєво відкликати сесію. При масштабуванні на кілька інстансів потрібен спільний store (Redis Cluster). Cookie з session ID — httpOnly, Secure, SameSite=Strict.
Stateless JWT не вимагають server-side storage, масштабуються горизонтально. Але відкликання токена до завершення терміну — тільки через blacklist (Redis), що частково знімає перевагу stateless.
| Параметр |
Сесії (серверний стан) |
JWT (stateless) |
| Відкликання |
миттєве (видалити запис у Redis) |
лише через blacklist, потребує storage |
| Масштабування |
потрібен спільний Redis |
горизонтальне без додаткових компонентів |
| Безпека XSS |
токен у httpOnly cookie захищений |
при зберіганні в localStorage — ризик |
| Складність реалізації |
проста (сесійний middleware) |
вища (управління refresh, ротація) |
Для більшості веб-додатків сесії простіші та безпечніші. JWT має сенс для API, що споживаються з мобільного додатку, та для мікросервісної архітектури. Оцініть ваш сценарій — ми допоможемо обрати правильний підхід.
OAuth 2.0 та OpenID Connect
OAuth 2.0 — протокол делегованої авторизації, не аутентифікації. «Увійти через Google» — це OpenID Connect поверх OAuth 2.0, який додає id_token з даними користувача. Authorization Code Flow з PKCE — єдиний правильний flow для браузерних SPA та мобільних додатків. Implicit Flow застарів і небезпечний. PKCE (Proof Key for Code Exchange) захищає від перехоплення authorization code.
Реалізація OAuth сервера: не пишемо з нуля. Keycloak (open source, self-hosted), Auth0, Okta — готові рішення. Laravel Passport або Laravel Sanctum для серверних додатків. NextAuth.js для Next.js — підтримує 50+ провайдерів з коробки. Для B2B продуктів з корпоративними клієнтами — SAML 2.0 SSO. Корпоративні IT-відділи часто вимагають його замість OAuth. @boxyhq/saml-jackson — node.js бібліотека для SAML → OAuth2 адаптера.
RBAC, ABAC, ReBAC — що і коли застосовувати
Role-Based Access Control — у користувача є ролі, у ролей — права. Проста реалізація: user → roles → permissions. Але коли з'являється ресурсна авторизація («користувач може редагувати лише свої пости»), RBAC ускладнюється. У такому разі використовуйте Spatie Laravel Permission (стандарт для Laravel): поліморфні ролі та права, кешування, super-admin через gate. Інтеграція з Eloquent — $user->can('edit posts'), $user->hasRole('editor').
ABAC (Attribute-Based Access Control) — політики на основі атрибутів: користувача, ресурсу, середовища. Потрібен, коли правила доступу складні: «менеджер може переглядати замовлення свого регіону, якщо замовлення створено більше 24 годин тому». Casbin — популярна cross-language бібліотека для ABAC.
ReBAC (Relationship-Based Access Control) — Google Zanzibar model. Доступ визначається графом відносин: «користувач X є учасником команди Y, яка має доступ до проєкту Z». OpenFGA — open source реалізація від Okta.
Як ми це робимо: кейс із впровадження 2FA
Проєкт — платіжний шлюз для маркетплейсу. Потрібно було захистити доступ до операцій виводу коштів. Ми спроєктували систему:
- Основний пароль замінили на комбінацію пароль + TOTP (Google Authenticator). Використали
otplib (Node.js) для генерації та верифікації кодів.
- Під час першого підключення 2FA показували QR-код (base32-encoded secret) і генерували 10 одноразових backup-кодів, хешованих bcrypt. Відображали коди лише один раз.
- Secret для TOTP зберігали у зашифрованому вигляді в базі даних (AES-256-GCM, ключ у AWS KMS).
- На стороні фронтенду інтегрували
@simplewebauthn/browser для passkeys — біометрична аутентифікація як альтернатива паролю. Публічний ключ зберігали на сервері, private key на пристрої користувача.
- Результат: час на логін зріс на 5 секунд, але кількість зламаних акаунтів упала до нуля за пів року роботи. Гарантія безпеки — на рівні OWASP ASVS Level 2.
Також варто зазначити, що TOTP значно надійніше за SMS-верифікацію через SIM-swapping, тому для фінансових даних ми рекомендуємо TOTP або апаратні ключі.
Типові вразливості, які ми знаходимо
- Broken Object Level Authorization (BOLA/IDOR): /api/orders/12345 повертає замовлення без перевірки, чи належить воно поточному користувачу. Найпоширеніша вразливість API за OWASP. Кожен запит до ресурсу — перевірка через $user->can('view', $order).
- Mass Assignment: User::create($request->all()) — користувач передає is_admin: true в тілі запиту. Laravel вирішує через $fillable / $guarded, але часто забувають.
- Небезпечний CORS: Access-Control-Allow-Origin: * на API з авторизацією по cookie — credentials не передаються з wildcard origin, але якщо хтось зробив Allow-Credentials: true + Allow-Origin: * — це діра.
Penetration testing обов’язковий для продуктів з фінансовими даними або персональними даними користувачів. Ми проводимо аудит коду й інфраструктури на етапі приймання.
Що входить у роботу
Ми передаємо замовнику:
- Документацію архітектури авторизації (flow діаграми, опис токенів, політик доступу).
- Репозиторій із вихідним кодом, покритий unit- та integration-тестами.
- Конфігурацію для CI/CD (GitHub Actions/ GitLab CI) із перевірками безпеки.
- Доступи до середовищ (staging, production) із правами адміністратора.
- Інструкцію з експлуатації та супроводу.
- Підтримку після впровадження — 2 тижні безкоштовних консультацій.
Терміни та вартість
Базова аутентифікація (email/password + OAuth + JWT/сесії): 1–3 тижні. RBAC з детальними політиками доступу: 2–4 тижні. 2FA (TOTP + SMS): 1–2 тижні. WebAuthn/Passkeys: 2–3 тижні. Повна система аутентифікації для SaaS з multi-tenancy: 4–8 тижнів.
Вартість розраховується індивідуально залежно від обсягу та складності. Замовте консультацію — оцінимо ваш проєкт безкоштовно. Отримайте гарантію безпеки вашої авторизації користувачів.