Junior: питання на співбесіді з теми «Захист і інтеграції»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
- Автентифікація відповідає на питання «хто ви?»: перевіряє токен, ключ, пароль.
- Авторизація - «що вам можна?»: чи має цей користувач право на цю дію з цим ресурсом.
Спершу автентифікація, потім авторизація. Помилки - різні: 401 - не вдалося встановити, хто ви; 403 - відомо, хто ви, але дія заборонена.
Способи автентифікації в API:
- API-ключ у заголовку (
X-API-KeyчиAuthorization: Bearer ...) - для серверних інтеграцій. - Токени користувачів - персональні токени доступу (Laravel Sanctum) чи токени OAuth 2.0.
- Cookie-сесія - для власного SPA на тому самому домені (Sanctum SPA-режим), з CSRF-захистом.
- JWT - самодостатній підписаний токен; сервер перевіряє підпис без запиту до бази, але відкликати такий токен до закінчення терміну складніше.
Авторизація - на кожному ендпоінті, на сервері:
public function update(UpdateOrderRequest $request, Order $order)
{
$this->authorize('update', $order); // чи це замовлення цього користувача
// ...
}
Найпоширеніша вразливість API (OWASP API Top 10, BOLA) - є автентифікація, але немає перевірки прав на конкретний об'єкт: GET /orders/43 повертає чуже замовлення, бо код перевірив лише, що користувач залогінений.
Ще правила: токени лише по HTTPS, ніколи в URL (потраплять у логи й історію браузера), з обмеженим терміном дії й мінімально потрібними правами (scopes).
Браузер дотримується same-origin policy: JavaScript зі сторінки https://shop.example не може прочитати відповідь з https://api.other.example. CORS (Cross-Origin Resource Sharing) - механізм, яким сервер дозволяє браузеру послабити це обмеження для певних джерел.
Access-Control-Allow-Origin: https://shop.example
Access-Control-Allow-Methods: GET, POST, PATCH
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Для «непростих» запитів (з Authorization, JSON-тілом, методами PUT/DELETE) браузер спершу надсилає preflight - OPTIONS-запит з питанням, чи можна. Access-Control-Max-Age дозволяє кешувати відповідь preflight.
Чому CORS - не захист API:
- Його виконує браузер.
curl, Postman, скрипт на сервері, мобільний застосунок CORS не перевіряють і отримують відповідь незалежно від заголовків. - CORS захищає користувача в браузері від того, щоб чужий сайт від його імені читав дані з вашого API. Але не захищає сам API від прямих запитів.
- Навіть із забороненим CORS простий запит (
POSTз формою) доходить до сервера й виконується - браузер лише не віддає скрипту відповідь. Тому від CSRF рятують токени й cookieSameSite, а не CORS.
Захист API - це автентифікація, авторизація, ліміти запитів, валідація.
Типові помилки налаштування:
Access-Control-Allow-Origin: *разом з credentials - браузер це не дозволяє.- Віддзеркалення будь-якого
Originз запиту у відповідь разом зAllow-Credentials: true- фактично дозволяє будь-якому сайту читати дані користувача.
Усі три способи відповідають на питання «хто робить запит», але для різних клієнтів.
Сесійна cookie - браузерна автентифікація:
- користувач входить, сервер створює сесію і ставить cookie (
HttpOnly,Secure,SameSite); - браузер сам додає cookie до кожного запиту;
- для кого: власний фронтенд на тому ж домені (Blade, Livewire, Inertia, SPA через Sanctum);
- переваги: токен недоступний JavaScript (захист від крадіжки через XSS), вихід - знищити сесію на сервері;
- потребує захисту від CSRF - бо браузер надсилає cookie автоматично.
Токен доступу (bearer token) - автентифікація від імені користувача для небраузерних клієнтів:
Authorization: Bearer 1|AbCdEf...
- для кого: мобільні застосунки, десктоп, CLI, сторонні клієнти від імені користувача (OAuth);
- токени можна обмежити правами (scopes/abilities) і терміном дії, відкликати окремо для кожного пристрою;
- CSRF не загрожує - браузер сам токен не додає.
API-ключ - ідентифікація застосунку-інтегратора, а не людини:
- для кого: сервер-сервер інтеграції (партнер вивантажує замовлення, CRM синхронізує контакти);
- зазвичай довгоживучий, прив'язаний до облікового запису клієнта, з власними лімітами й правами;
- передається в заголовку (
AuthorizationчиX-Api-Key), ніколи в URL.
Типові помилки:
- токени в
localStorageдля власного SPA - будь-який XSS їх вкраде. Для SPA на своєму домені cookie-сесія безпечніша; - API-ключ у мобільному застосунку чи фронтенді - будь-хто дістане його з бандлу. Ключі - лише на серверах;
- токени без терміну дії і без можливості відкликання;
- ключі в Git, логах, URL - сканери публічних репозиторіїв знаходять їх за хвилини.
Як зберігати на сервері: як паролі - лише хеш (порівняння з хешем отриманого значення). Тоді витік бази не дає готових ключів. Так зберігає токени Laravel Sanctum (SHA-256).
Найпоширеніша вразливість API за OWASP - не спосіб автентифікації, а відсутність перевірки доступу до конкретного об'єкта після автентифікації.
Через HTTP без шифрування кожен запит видно будь-кому на шляху: публічний Wi-Fi, провайдер, скомпрометований роутер. Токени в Authorization, cookie сесій, паролі в тілі запиту, персональні дані у відповідях - усе в відкритому вигляді. Крім читання, трафік можна змінити (вставити скрипт, підмінити відповідь).
HTTPS (TLS) дає:
- шифрування - вміст недоступний стороннім;
- цілісність - зміна трафіку буде виявлена;
- автентичність сервера - сертифікат підтверджує, що клієнт говорить саме з вашим доменом.
Правила для API:
- лише HTTPS, без «а на HTTP теж працює». Запит на HTTP краще відхиляти, а не перенаправляти: редирект
301для API означає, що токен уже пройшов відкритим каналом у першому запиті; - сертифікати - автоматичне оновлення (Let's Encrypt, Caddy, хмарні балансувальники), моніторинг терміну дії;
- сучасний TLS (1.2+, краще 1.3), без застарілих шифрів.
HSTS (Strict-Transport-Security) - заголовок, що каже браузеру: «з цим доменом - лише HTTPS, протягом указаного часу»:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Після першого візиту браузер сам переписуватиме http:// на https:// ще до відправки запиту - атака з «пониженням» до HTTP (SSL stripping) не спрацює. preload і включення домену в список браузерів захищає навіть перший візит.
Що варто знати:
- HSTS стосується браузерів. Мобільні застосунки й серверні клієнти його не читають - їх треба конфігурувати на HTTPS-адреси;
includeSubDomains- лише якщо всі піддомени готові до HTTPS, інакше вони стануть недоступними на весьmax-age;- за проксі (Cloudflare, балансувальник) TLS може закінчуватися на проксі. Laravel має довіряти заголовкам
X-Forwarded-Proto(trustProxies), інакше генеруватимеhttp://посилання й вважатиме запити незахищеними; - cookie з
Secureне передаються по HTTP взагалі.
Між внутрішніми сервісами теж варто шифрувати трафік: «внутрішня мережа» часто не така закрита, як здається, а для критичних з'єднань - взаємна автентифікація (mTLS).
Масове призначення (mass assignment) - коли API бере тіло запиту й записує його в модель цілком. Клієнт додає поле, якого форма не має, - і змінює те, що не повинен.
// небезпечно
$user->update($request->all());
PATCH /api/profile
{"name": "Оля", "is_admin": true, "balance": 1000000}
Дзеркальна проблема - зайві дані у відповіді: return $user; віддає всі атрибути моделі, включно з тими, що клієнту бачити не можна (внутрішні прапорці, хеші, токени, приховані поля інших користувачів). OWASP об'єднує обидві проблеми в категорію «порушена авторизація на рівні властивостей об'єкта».
Захист на вході:
// лише перевірені поля
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'bio' => ['nullable', 'string', 'max:1000'],
]);
$user->update($validated);
validate()/ Form Request повертає лише ті поля, для яких є правила, - решта ігнорується;$fillableу моделі - друга лінія захисту: навітьupdate($request->all())не запише поля поза списком.$guarded = []вимикає цей захист;- поля, залежні від прав (роль, статус модерації), - окремі ендпойнти чи явні перевірки: «змінювати
roleможе лише адміністратор».
Захист на виході:
return new UserResource($user); // явний перелік полів
- API Resource чи DTO з явним переліком полів замість серіалізації моделі;
$hiddenу моделі (паролі, токени) - корисно, але недостатньо: краще відповідь описувати явно;- поля за правами:
'email' => $this->when($request->user()->can('viewContacts', $this->resource), $this->email).
Ознаки проблеми під час рев'ю коду:
$request->all()чи$request->input()без валідації, передане вcreate/update;return $modelабо$model->toArray()у контролерах API;$guarded = []у моделях, що змінюються через API.
Тест, що варто мати: відправити зайве поле (is_admin: true) і перевірити, що воно не змінилося.
Докладніше в документації: OWASP API3:2023 - авторизація на рівні властивостей