Завантаження публікації
ОГОЛОШЕННЯ

Обмеження доступу в контролері та запитах: як не допустити витоку даних і “обхід” прав

Практичні правила для Laravel: де саме ставити authorize/can, як обмежувати списки на рівні SQL (а не в Blade), як не дати користувачу відкрити чужий запис по ID і як узгодити це з middleware та policies.


Максим Третяк
Максим Третяк
Газета Дейком | 02.01.2026, 14:00 GMT+3; 07:00 GMT-4

Критична помилка в адмінках — думати, що “доступ перевірив middleware, значить все ок”. Middleware зазвичай вирішує лише “чи можна зайти в модуль”. Але витоки і обходи трапляються в двох місцях: коли користувач бачить зайві записи в списках, і коли він може звернутися до конкретного запису напряму по URL. Тому доступ треба обмежувати одразу на двох рівнях: у контролері (авторизація дії) і в запитах (обмеження вибірки даних).

Почнемо з контролера. Контролер — це точка, де ви гарантовано бачите намір: “переглянути список”, “відкрити”, “створити”, “оновити”, “видалити”, “змінити статус”. Тут ви повинні робити явну авторизацію через policy/gate, а не через умовні if. Це дає стандартну поведінку (403), централізацію правил і менше шансів забути перевірку. Практично: у index викликається authorize('viewAny', Model::class), у show/edit/update/destroy — authorize('view', $model) / authorize('update', $model) / authorize('delete', $model). Якщо у вас є кастомні дії (approve/publish), для них мають бути окремі policy-методи і окремі authorize-виклики.

Дуже важливо: authorize має стояти ДО будь-яких побічних дій. Тобто спочатку ви перевіряєте право, а вже потім робите апдейт, відправляєте job, пишете activity. Інакше ви можете випадково виконати частину сценарію до того, як повернете 403, і отримаєте “напівдію” від користувача без прав.

Тепер головне — обмеження доступу в запитах. Навіть якщо ви правильно робите authorize на конкретному записі, ви можете вже “злити” зайві дані через список. Наприклад, користувач не повинен бачити чужі чернетки, але ваш index робить News::latest()->paginate(). У UI це буде видно, навіть якщо редагувати він не може. Це порушення доступу. Тому список має будуватися з урахуванням того, хто дивиться: author бачить свої записи, редактор — записи свого ресурсу, адміністратор — все.

Правило номер один: доступ обмежується в SQL, не в Blade. Тобто ви не робите “витягнути все, а потім в шаблоні сховати”. Це і небезпечно, і неефективно. Ви одразу в запиті додаєте where/join/whereIn, щоб недоступні записи фізично не потрапили в результат.

Є два надійні способи організувати це. Перший — scope на моделі (наприклад, scopeVisibleTo($query, $user)), який додає потрібні where залежно від ролі. Другий — окремий query builder/сервіс для списків, який будує запит з фільтрами і доступами. Обидва варіанти хороші. Головне — щоб це було в одному місці, а не по всіх контролерах.

Далі — захист від прямого доступу по ID. Класика: користувач бачить свій запис /admin/news/123/edit, і пробує /admin/news/124/edit. Якщо ви просто робите News::findOrFail(124) і віддаєте форму — це злам. Якщо ви робите authorize/update — ви отримаєте 403 і формально “захищено”. Але краще зробити ще міцніше: щоб він навіть не міг “знайти” запис, який йому недоступний. Для цього у show/edit ви піднімаєте запис не “в лоб”, а через той самий доступний scope: тобто “знайди запис серед доступних”. Тоді він отримає 404, і це часто правильніше з точки зору безпеки (не розкривати факт існування запису).

Практичний стандарт такий: для модулів з високою чутливістю (фінанси, договори, модерація) — краще “404 для недоступного”, тобто fetch через доступний набір. Для менш критичних — може бути і 403, але у вас уже видно, що доступи критичні, тому краще тягнути через доступний набір і мати однакову поведінку.

Окрема тема — фільтри і query string. Користувач може підставити ?user_id=999 або ?resource_id=… і спробувати “вибрати” чужі дані. Ваші фільтри не повинні розширювати доступ, вони можуть лише звужувати його. Це ключове правило. Тобто спочатку ви накладаєте базове обмеження видимості (хто що може бачити), а потім застосовуєте фільтри користувача. Якщо зробити навпаки або не мати базового обмеження, фільтр може стати інструментом витоку.

Ще одна критична точка — eager loading і пов’язані дані. Навіть якщо основний запис доступний, пов’язані дані можуть бути чутливими. Наприклад, новина доступна, але її activity log або фінансові поля — ні. Якщо ви робите with('activity') без обмежень, ви можете віддати зайве. Тому або не підвантажуйте чутливі зв’язки без потреби, або підвантажуйте їх через окремі authorize-перевірки/окремі endpoints, або робіть окремі ресурси/DTO для “публічного” і “адмінського” представлення.

На рівні контролера також важливо не довіряти полям, які приходять з форми, навіть після authorize. Наприклад, користувач має право редагувати текст, але не має права змінювати approval_status. Якщо він підставить це поле в request, а ви зробите $model->update($request->validated()), ви можете змінити заборонене поле. Тому обмеження доступу до полів робиться у сервісі: whitelist ключів залежно від прав. Це частина “обмеження доступу в запитах”, тільки на запис.

Стандартний “залізобетонний” ланцюжок виглядає так. Маршрут захищений middleware permissions, щоб випадкові не заходили. У контролері ви робите authorize на конкретну дію. Запит на читання будується через доступний набір (scope/query builder), щоб недоступне навіть не попадало у вибірку. А при записі сервіс формує ефективні дані (whitelist) і робить транзакцію для багатотабличних сценаріїв. Тоді “обхід” через URL, через query string і через підсунуті поля не працює.

Висновок: обмеження доступу — це не один if і не один middleware. Це система: авторизація дії в контролері плюс обмеження вибірки в SQL плюс обмеження полів при записі. Якщо ви вибудуєте це як стандарт, у вас не буде ситуацій “у цьому місці захищено, а в іншому забули”, і ви зможете додавати нові екрани та фільтри без страху, що вони випадково відкриють дані або дозволять небезпечні зміни.


Максим Третяк — Кореспондент, який спеціалізується на суспільно важливих темах, пише про політику, фінансові ринки та економіку. Він проживає та працює в Україні.

Цей матеріал є частиною розгорнутої теми: Web-програмування, яка охоплює численні цікаві аспекти цієї події. Газета «Дейком» ретельно відстежує події, проводячи перевірку джерел та інформації, щоб забезпечити нашим читачам найбільш точне та актуальне інформування.

Цей матеріал опубліковано 02.01.2026 року о 14:00 GMT+3 Київ; 07:00 GMT-4 Вашингтон, розділ: Освіта, із заголовком: "Обмеження доступу в контролері та запитах: як не допустити витоку даних і “обхід” прав". Якщо в публікації з'являться зміни, про це буде зазначено та описано у кінці публікації.

Читайте щоденну газету та загальну стрічку новин газети Дейком, яка поєднує багато цікавого в понад 40 розділах з усіх куточків світу.


Save
ОГОЛОШЕННЯ

Новини, які можуть Вас зацікавити:

Штатні та позаштатні журналісти газети «Дейком» щодня готують сотні публікацій, щоб читачі отримували найоперативнішу, перевірену й глибоку інформацію. Ми працюємо для тих, хто хоче розуміти суть подій, бачити широку картину та бути на крок попереду.

Останні новини

Вибір редакції