Авторизация: политики и Gate

Авторизация решает не «кто ты», а «что тебе можно»: политики и гейты задают правила доступа к действиям и конкретным записям.

Суть: аутентификация определяет личность, авторизация — права. Gate — простое правило-замыкание, Policy — класс с правилами для конкретной модели. Проверка идёт через can() и директиву @can.

Войти в систему и иметь право на действие — разные вещи. Пользователь может быть авторизован (вошёл), но это не значит, что ему позволено удалять чужие статьи или открывать админ-панель. За эти правила отвечает авторизация. Laravel предлагает два инструмента: гейты для простых проверок и политики для правил, привязанных к модели.

Gate — это именованное правило в виде замыкания: «может ли этот пользователь делать X». Policy — это класс, который группирует правила вокруг одной модели: кто может смотреть, обновлять, удалять конкретную запись. Политики — основной способ авторизации в реальных проектах, потому что обычно права связаны с владением: автор может править свою статью, но не чужую.

Политика модели

php artisan make:policy PostPolicy --model=Post
<?php
namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    // может ли пользователь обновить пост
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;   // только автор
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->is_admin;
    }
}

Проверка в контроллере и в шаблоне:

<?php
public function update(Request $request, Post $post)
{
    // бросит 403, если политика запретит
    $this->authorize('update', $post);

    $post->update($request->validated());
    return redirect()->route('posts.show', $post);
}
{{-- показать кнопку только тому, кому можно --}}
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Редактировать</a>
@endcan

Как работает под капотом

Когда вы вызываете $this->authorize('update', $post) или @can('update', $post), Laravel находит политику для модели Post и вызывает её одноимённый метод, передавая текущего пользователя и объект. Метод возвращает true или false. При false в контроллере выбрасывается ответ 403, а в Blade блок просто не отрисовывается. Так логика прав живёт в одном месте, а не размазана по контроллерам и шаблонам.

  @can('update', $post)
        |
        v
  PostPolicy::update($currentUser, $post)
        |
   user.id == post.user_id ?
        |                  \
      true                false
        |                    |
   показать кнопку      скрыть / 403

Смоделируем политику на Python: правило сравнивает владельца записи и текущего пользователя.

Попробуй сам ▶

# Policy: кто может редактировать пост
def can_update(user, post):
    return user['id'] == post['user_id'] or user.get('is_admin')

author = {'id': 1, 'is_admin': False}
stranger = {'id': 2, 'is_admin': False}
admin = {'id': 9, 'is_admin': True}
post = {'id': 100, 'user_id': 1}

for name, u in [('автор', author), ('чужой', stranger), ('админ', admin)]:
    verdict = 'можно' if can_update(u, post) else '403 запрещено'
    print(f'{name:6} -> {verdict}')

Частые ошибки

  • Путать с аутентификацией. auth-middleware пускает вошедших, но не проверяет права на конкретный объект — это делают политики.
  • Проверять права только в Blade. Скрытая кнопка не защищает маршрут; обязательно вызывайте authorize() и в контроллере.
  • Дублировать условия доступа. Если правило «только автор» повторяется в коде — его место в политике.

Best practices

  • Для правил, связанных с моделью, используйте Policy; для общих — Gate.
  • Проверяйте доступ и в контроллере (authorize), и в шаблоне (@can) — защита плюс удобство.
  • Метод before() в политике удобно даёт супер-админу доступ ко всему.

У политик есть несколько приёмов, без которых реальная авторизация неполна. Метод before() в классе политики выполняется раньше всех остальных и может «коротко замкнуть» проверку: если в нём вернуть true для администратора, он получит доступ ко всем действиям, минуя частные правила. Это избавляет от добавления проверки «или админ» в каждый метод. Для действий, не привязанных к конкретной записи (например, «создать пост» — записи-то ещё нет), метод политики принимает только пользователя: create(User $user). Помимо политик, есть глобальные гейты, которые регистрируют в сервис-провайдере через Gate::define('view-reports', fn($user) => $user->is_manager) — они удобны для прав, не связанных с моделью. И аутентификация, и авторизация в Laravel спроектированы так, чтобы правила доступа жили в одном месте, а контроллеры и шаблоны лишь спрашивали разрешения, не зная деталей реализации.

Итог: авторизация отвечает на вопрос «что можно», а политики и гейты держат эти правила в одном месте. Дальше соберём всё изученное в полном CRUD-приложении.

Проверьте себя
1. Чем авторизация отличается от аутентификации?
AНичем
BАутентификация определяет личность («кто ты»), авторизация — права («что можно»)
CАвторизация — это вход по паролю
DАутентификация проверяет права на запись
2. Для чего служит Policy (политика)?
AХранит пароли
BГруппирует правила доступа к действиям над конкретной моделью
CСоздаёт миграции
DРендерит HTML