Authorization in Laravel Gate and Police

Authorization in Laravel is one of those features that separates a basic project from a truly professional one. In this article, I am going to explain to you, with real examples and functional code, how to use Gates and Policies to control user access in your application, using, of course, the system of Localizations and Translations in Laravel to correctly convey the message.

We are going to look at an introduction to Gates in Laravel, which allow you to manage user authorization, that is, to indicate which parts of the system users can access based on imposed rules.

Introduction to Authorization in Laravel

Difference between authentication and authorization

Before touching code, it must be clear that authentication and authorization are not the same thing.

  • Authentication: Authentication refers to when the user provides their credentials (username/password) at the system level, that is, the login is performed.
  • Authorization: Authorization refers to what the user can do, that is, setting limits; previously we saw how to do this process with administrator and regular roles, but, in this opportunity, we are using a Laravel-specific service known as Gate.

In my case, I understood it better by imagining a Gate as a door. If the user has permission, the door opens; if not, it remains closed. Laravel precisely implements that concept: a Gate decides who can pass and who cannot.

Why use Gates and Policies instead of traditional roles?

Using roles like "admin" or "regular user" can work at the beginning, but it doesn't scale well. Gates and Policies allow you to define more precise and centralized rules, facilitating code maintenance and security.

What are Gates in Laravel

Definition and basic operation

Gates are functions that determine whether a user can execute a specific action. They are normally defined in the file:

app/Providers/AuthServiceProvider.php

How to create a Gate in AuthServiceProvider

For example, this Gate allows updating only the posts created by the same user:

use Illuminate\Support\Facades\Gate;
Gate::define('update-post', function ($user, $post) {
   return $user->id == $post->user_id;
});

You can imagine a Gate as a door (hence its name) in which, if a user has access to certain parts of the system, it means the door is open; if they do not have access, then the door remains closed and they will not be able to access those parts of the system.

Here we can see 3 important elements:

  1. The use of a Facade: Illuminate\Support\Facades\Gate.
  2. Defining a key to indicate in a nutshell what the operation is going to perform. In the previous example, it would be "update-post", which would be for updating a post. Therefore, we can use it in the controllers to edit a form (The edit() and update() functions).
  3. The next element corresponds to the rule or rules you want to impose. In the previous example, we indicated that the post must belong to a user, but you can add more, such as asking for the user's role.

Another important point is the arguments. To be able to use authorization, the user must be authenticated, which is why the user argument is always present and is supplied internally by Laravel; the rest of the arguments are completely customizable, and the ones you define depend on the rules you are going to establish.

Practical Example: Restrict Post Editing

Video thumbnail

To be able to do some examples with Gates, we will need to make some changes to the project, for example, adding a user ID column to the posts table; to do this, we create a migration:

$ php artisan make:migration add_user_id_to_posts_table

Test data with PostFactory:

'user_id' => $this->faker->randomElement([1, 2]),

This allowed the posts to be correctly linked to users and to test the Gate without errors.

We define the new column:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreignId('user_id')->constrained()
                ->onDelete('cascade');
        });
    }

    /
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('user_id');
        });
    }
};

And we execute the migration command:

$ php artisan migrate:fresh

This command deletes all tables and regenerates them. We do this because there are existing posts in the database and we cannot add a new foreign key (not null) column to existing posts.

We apply the changes to the model:

app\Models\Post.php

class Post extends Model
{
    use HasFactory;

    protected $fillable = [*, 'user_id'];
    *
}

And in the factory, we add the user ID field:

database\factories\PostFactory.php

class PostFactory extends Factory
{
    public function definition(): array
    {
        // Post::truncate();
        $name = fake()->sentence;
        return [
            'title' => $name,
            'slug' => str($name)->slug(),
            'content' => $this->faker->paragraphs(20, true),
            'description' => $this->faker->paragraphs(4, true),
            'category_id' => $this->faker->randomElement([1, 2, 3]),
            'user_id' => $this->faker->randomElement([1, 2]),
            'posted' => $this->faker->randomElement(['yes', 'not']),
            'image' => $this->faker->imageUrl()
        ];
    }
}

With this, we have the initial changes ready to be able to create our first rule using the Gate.

Gate define and allow, key methods

Video thumbnail

There are two very important methods in Gates. The first one is define() to define the Gate with the rules just as we saw before:

app\Providers\AuthServiceProvider.php

public function boot(): void
{
    *
    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}

As you can see in the previous code, Gates are defined in AuthServiceProvider.php.

And to be able to use the previous Gate, we use the allows() function, since the previous Gate is to prevent users from modifying other users' posts, we use it in the editing methods:

app\Http\Controllers\Dashboard\PostController.php

use Illuminate\Support\Facades\Gate;
*
class PostController extends Controller
{
    public function edit(Post $post): View
    {
        if (!Gate::allows('update-post', $post)) {
            return abort(403);
        }
        *
    }

    public function update(PutRequest $request, Post $post): RedirectResponse
    {
        if (!Gate::allows('update-post', $post)) {
            return abort(403);
        }
        *
    }
}

And you will see that, from the Dashboard, when you try to modify a post that does not belong to a user, a 403 error appears, which you can of course customize with a redirection or any other operation.

What are Policies in Laravel

When to use a Policy

Policies are classes dedicated to handling the authorization for an entire model.
If the Gate is a "specific door," a Policy is "the guard of the whole building."

It is convenient to use Policies when:

  • You have many rules for the same model.
  • You want to keep the code tidy.
  • You want to take advantage of automatic methods like viewAny, update, delete, etc.
  • Creating a Policy with Artisan step by step

Laravel makes it simple:

$ php artisan make:policy PostPolicy --model=Post

This creates a file in app/Policies/PostPolicy.php with base methods.

Registering and applying a Policy to a model

Edit your AuthServiceProvider and register the class:

protected $policies = [
   Post::class => PostPolicy::class,
];

Now Laravel knows which Policy to apply for that model.

Complete example of authorization with Policy

public function update(User $user, Post $post)
{
   return $user->id === $post->user_id;
}

And in the controller:

$this->authorize('update', $post);

Since I started using Policies, my code has become cleaner and much easier to maintain.

Using Gates and Policies in controllers and views

Using Gate::allows() and can() in controllers

In controllers, you can authorize like this:

if (Gate::denies('update-post', $post)) {
   abort(403);
}

Or use the more readable helper:

if ($user->can('update', $post)) { // allowed action }

Direct Authorization in Blade with @can and @cannot

In Blade views, Laravel allows you to simplify it with directives:

@can('update', $post)
   <a href="{{ route('posts.edit', $post) }}">Editar</a>
@endcan

Integration with middleware to protect routes

You can also use middleware on the routes:

Route::put('/posts/{post}', [PostController::class, 'update'])
   ->middleware('can:update,post');

This prevents unauthorized users from even reaching the controller.

⚠️ Common Errors and How to Avoid Them

403 Error when applying a Gate

Often it's not a Gate failure, but that the user does not meet the conditions. Make sure $user and $post are correctly passed.

Migration and foreign key issues

When I added user_id to my table, I had to run:

$ php artisan migrate:fresh

To regenerate the database without key conflicts.

Policies not registered or incorrectly linked

If your Policy does not execute, check that it is registered in AuthServiceProvider and that you use the exact same model.

Best Practices and Experience Tips

  • Keep rules centralized in Policies
    • Avoid dispersing the logic through controllers. Centralize everything in your Policies.
  • Name Gates and Policies coherently
    • Use short, descriptive names: update-post, delete-comment, publish-article.
  • Use testing to verify authorization
    • Laravel allows testing Gates easily:
    • $this->assertTrue(Gate::forUser($user)->allows('update-post', $post));
  • Combine roles and Policies for complex projects
    • In large systems, I usually combine roles with Policies to gain more granular access control.

Conclusion

Laravel offers a robust, secure, and elegant authorization system.
Gates serve you for specific rules; Policies, for complete models.
When you combine both, you achieve a clean, centralized, and easy-to-maintain flow.
In my experience, mastering these tools gives you a huge advantage in any real Laravel project.

❓ Frequently Asked Questions

What is the difference between a Gate and a Policy in Laravel?
A Gate handles a specific action; a Policy, the actions of an entire model.

Where are Gates defined in Laravel?
Usually in app/Providers/AuthServiceProvider.php.

How to handle a 403 error?
Verify that the user is authenticated and meets the conditions of the Gate or Policy.

Next step, the domain/subdomain management system in Laravel

I agree to receive announcements of interest about this Blog.

Gates are an authorization feature that allow you to define policies to control access to certain parts of your application.

| 👤 Andrés Cruz

🇪🇸 En español