Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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).

Докладніше в документації: HTTP-автентифікація

Браузер дотримується 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 рятують токени й cookie SameSite, а не CORS.

Захист API - це автентифікація, авторизація, ліміти запитів, валідація.

Типові помилки налаштування:

  • Access-Control-Allow-Origin: * разом з credentials - браузер це не дозволяє.
  • Віддзеркалення будь-якого Origin з запиту у відповідь разом з Allow-Credentials: true - фактично дозволяє будь-якому сайту читати дані користувача.

Докладніше в документації: CORS

Усі три способи відповідають на питання «хто робить запит», але для різних клієнтів.

Сесійна 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 - не спосіб автентифікації, а відсутність перевірки доступу до конкретного об'єкта після автентифікації.

Докладніше в документації: OWASP API Security Top 10 (2023)

Через 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).

Докладніше в документації: Strict-Transport-Security

Масове призначення (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 - авторизація на рівні властивостей