Middle: питання на співбесіді з теми «Авторизація»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
- Gate - замикання для простих, не прив'язаних до моделі перевірок.
- Policy - клас, що групує правила авторизації навколо конкретної моделі.
// Gate
Gate::define('view-admin', fn (User $u) => $u->is_admin);
// Policy
class PostPolicy
{
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
Застосування:
$this->authorize('update', $post); // у контролері
@can('update', $post) ... @endcan // у Blade
$user->can('update', $post); // будь-де
Політики автоматично відкривають 403 при відмові.
bool каже лише «можна» чи «ні». Response дає ще й пояснення та керує тим, що побачить користувач.
Повідомлення про причину:
public function update(User $user, Post $post): Response
{
if ($post->is_locked) {
return Response::deny('Статтю заблоковано модератором.');
}
return $user->id === $post->user_id
? Response::allow()
: Response::deny('Ви не автор цієї статті.');
}
Коли Gate::authorize('update', $post) кидає AuthorizationException, це повідомлення потрапляє у відповідь 403. Без винятку його можна прочитати через Gate::inspect() - наприклад, щоб показати підказку біля неактивної кнопки.
404 замість 403:
return $user->id === $invoice->user_id
? Response::allow()
: Response::denyAsNotFound();
403 підтверджує, що рахунок з таким номером існує. Для приватних ресурсів 404 нічого не розкриває - стороння людина не відрізнить чужий рахунок від неіснуючого. Є й denyWithStatus() для довільного коду.
Коли досить bool: коли причина очевидна й однакова - «не власник». Response окупається, де причин кілька або сам факт існування ресурсу приватний.
Усі три способи ведуть до тієї самої політики - різниця в тому, коли перевірка спрацьовує і які дані їй доступні.
Middleware can - найраніше, ще до контролера:
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Модель береться з прив'язки маршруту. Добре, коли рішення залежить лише від користувача й моделі з URL.
Form Request authorize() - перед валідацією:
public function authorize(): bool
{
return $this->user()->can('update', $this->route('post'));
}
Зручно, коли запит і так має Form Request: права й правила даних в одному класі.
У контролері Gate::authorize() - коли рішення залежить від чогось, що з'являється лише в коді: від даних запиту чи від іншої моделі.
Gate::authorize('transfer', [$account, $request->integer('amount')]);
У Laravel 11+ базовий контролер порожній, тож $this->authorize() доступний лише з трейтом AuthorizesRequests.
Що важливо незалежно від способу: перевірка має бути на сервері для кожної дії. Схована кнопка в Blade чи Livewire не захищає - запит можна відправити напряму. І в Livewire публічний метод компонента - теж ендпойнт, який перевіряє права сам.
Через перевірку, що виконується перед усіма іншими.
Для всього застосунку - Gate::before():
// AppServiceProvider::boot()
Gate::before(function (User $user, string $ability) {
return $user->isSuperAdmin() ? true : null;
});
Для однієї політики - метод before():
public function before(User $user, string $ability): ?bool
{
return $user->isSuperAdmin() ? true : null;
}
Головне - повертати null, а не false, для всіх, хто не суперадмін. null означає «не вирішую, перевіряй далі», а false заборонив би дію всім іншим, не дійшовши до політики.
Gate::after() - навпаки, спрацьовує після перевірок і впливає на результат, лише якщо гейт чи політика повернули null. Зручно для правила за замовчуванням.
Обережно з «усім можна»:
- суперадмін через
beforeобійде навіть правила, які мали б діяти для всіх, - наприклад, «не можна видалити оплачене замовлення»; такі бізнес-обмеження краще перевіряти окремо від прав; - дії суперадміна варто журналювати;
- для гнучкої моделі ролей і прав зазвичай беруть пакет на кшталт spatie/laravel-permission, а
beforeлишають для однієї надролі.