Skip to main content
Create your own
Lesson illustration

Implementing Authorization with Gates

Hello! Let's get started on the next step in our journey through Laravel's security features.

In our last lesson, you successfully built a token-based authentication system using Laravel Sanctum. This system answers the question, "Who is this user?" by verifying their identity. Now, we're going to tackle the next crucial question: "What is this user allowed to do?" This is the domain of authorization.

This lesson will introduce you to Laravel Gates, your first tool for defining authorization rules. You will learn how to create simple, closure-based permission checks and apply them in different parts of your application—from controllers to UI templates—to ensure users can only perform actions they are permitted to.

1. Authentication vs. Authorization

Before we write any code, it's essential to solidify the difference between authentication and authorization. Authentication is the process of verifying who someone is (e.g., checking a username and password). Authorization is the process of verifying what an authenticated user is allowed to do.

Authorization in Laravel: Can You Do That?

The official Laravel channel has a short, excellent video that uses a concert ticket analogy to explain this distinction. It also provides a high-level overview of Gates and Policies, which we'll cover in this lesson and the next.

Watch the first 1 minute and 29 seconds of this video to get a clear conceptual understanding of authentication vs. authorization.

As the video explains, getting into the concert is authentication. Which areas you can access with your ticket (general admission, VIP, backstage) is authorization.

Laravel Authorization: Gates and Policies Overview
Laravel provides two primary mechanisms for authorization: Gates and Policies. Today, we focus on Gates.

2. The Problem: Scattered Authorization Logic

Imagine you have an application where users can create job postings. A user should only be allowed to edit a job posting that they themselves created. A common but messy way to handle this is to put the logic directly inside your controller.

30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery

Let's watch a practical example of this problem. In this segment from a Laracasts video, Jeffrey Way first implements authorization logic directly inside a controller method. This will highlight why we need a more organized approach.

Watch from 01:56 to 05:44. Pay attention to the if statements being added to the edit method. Notice how this logic is now 'stuck' inside the controller.

This inline approach has several drawbacks:

  • Repetition: If you also need to check this permission in the update or delete methods, you have to duplicate the logic.
  • Scattered Logic: Your authorization rules are spread across different controller methods instead of being in one central place.
  • UI Challenges: How do you hide the "Edit" button in your view for a job the user doesn't own? The logic isn't easily accessible from the view.

To solve these problems, we can extract this logic into a Gate.

3. Defining Your First Gate

A Gate is a simple closure that receives the currently authenticated user and returns true or false. It's a named authorization rule. Gates are typically defined in the boot method of your App\Providers\AppServiceProvider.

Authorization - Laravel 12.x

Let's turn to the official documentation to see the formal definition of a Gate and its syntax.

Read the sections 'Gates' and 'Writing Gates'. Focus on the example of the 'update-post' Gate. This shows the standard structure: Gate::define('ability-name', function ($user, $model) { ... });.

Now let's see this in action. We'll take the messy logic from the controller and move it into a Gate.

30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery

Continuing with the Laracasts video, watch how the inline check is refactored into a reusable Gate named 'edit-job'.

Watch from 05:44 to 10:13. This is a critical section. Notice how the logic is moved to AppServiceProvider and the controller is cleaned up to use Gate::authorize().

Let's break down the process.

1. Define the Gate in app/Providers/AppServiceProvider.php:

<?php

namespace App\Providers;

use App\Models\Job; // Assuming you have a Job model
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Gate::define('edit-job', function (User $user, Job $job) {
            // This assumes a relationship where a Job belongs to an Employer,
            // which in turn belongs to a User. Adjust to your schema.
            return $user->id === $job->employer->user_id;
        });
    }
}

Key Points:

  • Laravel automatically passes the authenticated User as the first argument.
  • Any additional arguments you provide when checking the Gate (in this case, the $job model) are passed after the user.
  • If a user is not authenticated, the Gate will automatically return false.

4. Using Gates to Authorize Actions

Once defined, you can use your Gate in several places to protect your application.

In Controllers

Instead of a manual if check, you can use the Gate facade.

  • Gate::allows('edit-job', $job): Returns true or false.
  • Gate::denies('edit-job', $job): Returns true or false (the opposite of allows).
  • Gate::authorize('edit-job', $job): This is often the best choice. It will automatically throw an AuthorizationException, which Laravel renders as a 403 Forbidden HTTP response if the Gate returns false.

Here is the cleaned-up controller method:

use Illuminate\Support\Facades\Gate;

public function edit(Job $job)
{
    // If the user is not authorized, this line will throw a 403 exception.
    Gate::authorize('edit-job', $job);

    // If we reach this point, the user is authorized.
    return view('jobs.edit', ['job' => $job]);
}

This is much cleaner and more expressive.

In Blade Templates

To solve the problem of hiding the "Edit" button, you can use the @can Blade directive.

30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery

Now let's fix the UI. This segment shows how to use the @can directive in a Blade file to conditionally render HTML based on a Gate's result.

Watch from 11:06 to 12:07. See how the edit button is wrapped in an @can block, making the UI dynamically respond to the user's permissions.

Here's how it looks in your view (e.g., resources/views/jobs/show.blade.php):

{{-- The user will only see this button if the 'edit-job' Gate returns true for the given $job --}}
@can('edit-job', $job)
    <a href="/jobs/{{ $job->id }}/edit">Edit Job</a>
@endcan

{{-- You can also use @cannot --}}
@cannot('edit-job', $job)
    <p>You do not have permission to edit this job.</p>
@endcannot

In Route Definitions

For some actions, you might want to perform the authorization check before the request even hits your controller. You can do this by applying authorization middleware directly to your routes in routes/web.php or routes/api.php.

30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery

For an even cleaner controller, we can move the authorization check to the route definition itself. This part of the video explains how to use the can middleware.

Watch from 12:07 to 18:32. This section is a bit longer because it also discusses refactoring route resources. The key takeaway is learning how to use ->can('ability', 'model') on a route definition.

Here's how you would protect the edit and update routes:

// In routes/web.php

// The 'can' middleware will run the 'edit-job' Gate before calling the controller.
// Laravel automatically uses the 'job' route parameter.
Route::get('/jobs/{job}/edit', [JobController::class, 'edit'])
    ->middleware('auth') // Make sure user is authenticated first
    ->can('edit-job', 'job'); // Then check authorization

Route::patch('/jobs/{job}', [JobController::class, 'update'])
    ->middleware('auth')
    ->can('edit-job', 'job');

With this approach, you can remove Gate::authorize() from your controller entirely, as the authorization is handled before the controller method is even called.

// The controller method is now just responsible for its core logic.
public function edit(Job $job)
{
    return view('jobs.edit', ['job' => $job]);
}

5. Gate Nuances for Mastery

To fully master Gates, it's worth knowing a couple of extra features.

  • Passing Additional Arguments: A Gate can accept more than just the user and a model. Any extra parameters are passed in an array. This is useful for more complex rules.
  • Intercepting Checks: You can use Gate::before() to define a check that runs before all other Gates. This is perfect for a "super admin" who should always be authorized.
  • Custom Responses: Instead of just true or false, a Gate can return an Illuminate\Auth\Access\Response to provide a custom error message or even a custom HTTP status code.

Authorization - Laravel 12.x

The official documentation covers these advanced cases well. A quick read will make you aware of the full capabilities of Gates.

Skim the sections 'Supplying Additional Context', 'Gate Responses', and 'Intercepting Gate Checks'. Pay special attention to the Gate::before() example, as it's a very common pattern.

Here's an example of a before check for a super admin in AppServiceProvider:

public function boot(): void
{
    // This runs before any other Gate check.
    Gate::before(function (User $user, string $ability) {
        // Assuming you have an 'is_admin' property on your User model
        if ($user->is_admin) {
            return true; // The admin can do anything.
        }
    });

    Gate::define('edit-job', function (User $user, Job $job) {
        return $user->id === $job->employer->user_id;
    });
}

Conclusion

In this lesson, you've learned how to define and use Laravel Gates to create clear, reusable, and centralized authorization rules. This is a fundamental skill for building secure and well-structured applications.

Key Takeaways:

  • Authorization vs. Authentication: Authorization is about what a user can do, while authentication is about who they are.
  • Gates Centralize Logic: Gates are simple, named closures defined in AppServiceProvider that encapsulate a single permission check.
  • Authorize Everywhere: You can enforce a Gate's rule in multiple places:
    • Controllers: Using Gate::authorize() to protect an action and throw a 403 error.
    • Blade Templates: Using the @can directive to conditionally show UI elements.
    • Routes: Using the can middleware to protect an entire endpoint.
  • Super Admin: The Gate::before() method is the conventional way to grant all permissions to an administrator-level user.

Up Next:

Gates are excellent for simple, action-based permissions (e.g., access-admin-panel) or one-off checks. However, when you have a model with many associated permissions (create, view, update, delete, restore, etc.), grouping all of them as separate Gates can become disorganized.

In our next lesson, we will learn about Policies. Policies are classes that group all authorization logic for a specific model, providing a more structured and object-oriented approach to authorization for your application's resources.

Can't find a good explanation? Sign up and we'll make it for you

Sign up