Успішна перевірка облікових даних легко створює відчуття, що головна перевірка безпеки вже пройдена.
Система знає, хто звернувся. Ключ або інший доказ перевірений. Контекст встановлений.
Але залишається друге питання: чи має цей учасник право виконати саме цю операцію?
Для мене це дві окремі межі.
Автентифікація встановлює перевірену сторону взаємодії. Авторизація вирішує, які дії дозволені цій стороні в конкретному контексті.
Я розділяю два різні питання
Автентифікація Хто звернувся? | v перевірений контекст Авторизація Чи дозволена саме ця дія? | v дозволити або відмовитиЦі питання пов’язані, але не однакові.
Авторизація часто потребує результату автентифікації, тому що спочатку потрібно знати, для кого перевіряється доступ.
Але зворотний висновок неправильний: якщо сторона успішно автентифікована, з цього ще не випливає, що їй дозволена будь-яка дія.
Після автентифікації я отримую контекст, а не повний доступ
final readonly class AuthenticatedContext{ public function __construct( public string $actorId, public string $environment, ) {}}Результатом автентифікації може бути невеликий об’єкт із даними, яким система вже довіряє в межах цього запиту.
Наприклад, він може містити внутрішній ідентифікатор сторони та середовище, у якому вона була перевірена.
Але я не намагаюся зробити такий контекст доказом усіх можливих прав.
Його задача простіша: сказати наступному кроку, для кого саме потрібно перевірити право на операцію.
Небезпечний скорочений шлях: valid означає allowed
$context = $authenticator->authenticate( $request,); if ($context !== null) { $service->performSensitiveOperation( $input, );}У такому коді існування $context непомітно стає дозволом на дію.
Якщо сьогодні всі автентифіковані сторони справді мають однакові права, помилка може довго залишатися непомітною.
Проблема з’явиться пізніше, коли виникнуть операції з різними вимогами до доступу.
Тоді доведеться шукати всі місця, де успішна автентифікація вже використовується як неявна авторизація.
Право на операцію я перевіряю окремо
$context = $authenticator->authenticate( $request,); $authorizer->ensureAllowed( $context, Operation::SensitiveAction,); $service->execute( $context, $input,);Мені подобається, коли ця межа видима в коді.
Спочатку система встановлює перевірений контекст.
Потім окремий крок вирішує, чи дозволена конкретна операція.
Лише після цього запускається прикладний сценарій.
Така структура не вимагає великої універсальної системи ролей. Навіть одна важлива операція може мати власну чітку передумову доступу.
Одна сторона може мати різні права на різні дії
Перевірена сторона | +--> прочитати дані ALLOW | +--> змінити налаштування DENY | +--> виконати чутливу дію DENYСаме тут особливо добре видно різницю.
Система не починає сумніватися в тому, хто звернувся, лише тому, що одна операція заборонена.
Сторона може бути успішно автентифікована і мати право читати певні дані, але не мати права змінювати їх або запускати чутливу операцію.
Це нормальний стан, а не помилка автентифікації.
401 і 403 теж відображають цю різницю
Немає прийнятної автентифікації | v401 Unauthorized Автентифікація успішна,але операція не дозволена | v403 ForbiddenНа рівні HTTP ця межа теж має значення.
401 використовується, коли потрібна автентифікація відсутня або не пройдена.
403 описує іншу ситуацію: система вже має автентифікований контекст, але запитана дія не дозволена.
Ці статуси не повинні замінювати всю внутрішню модель безпеки, але вони добре показують, що "хто ти?" і "що тобі можна?" є різними питаннями.
Право на дію може залежати не лише від ідентичності
Авторизація часто потребує більше інформації, ніж просто ідентифікатор сторони.
Рішення може залежати від:
- конкретної операції;
- ресурсу, над яким виконується дія;
- поточного стану ресурсу;
- середовища;
- явно наданого права;
- окремих обмежень доступу до функції або можливості продукту.
Тому я не намагаюся вмістити весь майбутній доступ у сам результат автентифікації.
Сам факт володіння ключем не повинен мати зайвих значень
Ще одна причина розділяти ці перевірки - не перевантажувати облікові дані зайвою семантикою.
Ключ, токен або інший доказ може мати дуже конкретне призначення: підтвердити певну сторону взаємодії.
Якщо з самого факту володіння цим доказом автоматично виводяться всі дозволи, він починає одночасно бути ідентичністю, правом доступу і дозволом на будь-які майбутні операції.
Таку модель значно важче змінювати та обмежувати.
Авторизація повинна бути встановлена до привілейованої дії
HTTP request | vавтентифікація | vперевірений контекст | vавторизація операції | +--> DENY | +--> ALLOW | vДля чутливих операцій мені подобається, коли прикладний сценарій отримує вже встановлену передумову доступу або викликається тільки після явної перевірки.
Це дозволяє не змішувати в одному методі перевірку облікових даних, пошук ресурсу, правила доступу і саму зміну стану.
Головне тут не конкретна кількість класів, а те, щоб дозвіл не виникав неявно лише тому, що попередній крок завершився успішно.
Клієнт не повинен сам вирішувати, що йому дозволено
Зовнішній запит може передати дані, необхідні для виконання операції, але остаточне рішення про доступ має залишатися на стороні системи.
Наприклад, я не довіряю полю на кшталт "isAdmin": true лише тому, що його надіслав вже автентифікований клієнт.
Автентифікація підтверджує сторону взаємодії. Вона не робить довіреними всі твердження, які ця сторона передає у запиті.
Відмова в доступі не повинна розкривати зайве
{ "type": "https://api.example.com/problems/operation-forbidden", "title": "Operation forbidden", "status": 403, "code": "operation_forbidden", "correlationId": "..."}Окрема авторизація також допомагає чіткіше формувати зовнішні помилки.
Клієнту достатньо знати, що операція не дозволена.
Внутрішня причина може бути точнішою, але вона не повинна автоматично ставати частиною публічного контракту.
У прикладі назви й коди навмисно умовні.
Що я перевіряю в чутливому API-сценарії
Для операцій із підвищеними вимогами до доступу я ставлю кілька окремих питань:
- Що саме доводить автентифікація?
- Який контекст після неї можна вважати перевіреним?
- Чи не виводжу я дозвіл на дію лише з факту успішної автентифікації?
- Де приймається рішення про право на конкретну операцію?
- Від яких даних залежить це рішення?
- Чи залишається рішення на стороні системи, а не клієнта?
- Чи не розкриває відмова зайвий внутрішній стан?
Хто ти і що тобі дозволено - це різні межі
Для простого API автентифікація та доступ іноді виглядають як одна перевірка.
Але щойно з’являються різні типи операцій, чутливі зміни або окремі правила доступу, ця модель перестає масштабуватися.
Я залишаю автентифікації вузьку відповідальність: встановити перевірений контекст.
Авторизація окремо відповідає за те, чи дозволена конкретна дія цьому контексту.
А інші обмеження продукту можуть залишатися ще окремими правилами, якщо вони відповідають на інші питання.
Успішне "я знаю, хто це" для мене ніколи автоматично не означає "цій стороні дозволено все".
Автентифікація встановлює сторону взаємодії. Авторизація встановлює межі того, що цій стороні дозволено зробити.



