Welcome back! In our last two lessons, we built a solid foundation for authorization. We started with simple, closure-based Gates and then graduated to creating and registering structured, model-centric Policies. You now know how to define the rules of who can do what.
Today, we'll focus on applying those rules. This lesson is all about putting your Gates and Policies to work to secure your application. You will learn how to authorize user actions in the two primary places this is needed:
- In Blade templates: To control what UI elements a user sees.
- In controllers: To protect your backend logic and data from unauthorized access.
By the end of this lesson, you'll be able to confidently implement authorization checks throughout your Laravel application, creating a secure and user-friendly experience.
1. Authorizing Actions in Blade Templates
The first line of defense in user authorization is the user interface. If a user isn't permitted to edit a post, they shouldn't even see the "Edit" button. This prevents confusion and provides a cleaner experience.
Laravel provides simple and elegant Blade directives, @can and @cannot, to handle this. These directives check if the currently authenticated user is authorized to perform a given action.
30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery
This video from Laracasts provides a perfect practical demonstration. After setting up a Gate, it shows how to use the @can directive to conditionally display an 'Edit Job' button based on whether the user is authorized.
Watch the segment from 10:13 to 12:07. Pay close attention to how the @can directive is used in the Blade template to wrap the edit button.
As you saw, the implementation is straightforward. You wrap the HTML you want to conditionally display within the @can block.
{{-- In a view like resources/views/posts/show.blade.php --}}
{{-- Assume $post is the model instance passed to the view --}}
<h1>{{ $post->title }}</h1>
<p>{{ $post->body }}</p>
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}" class="btn btn-primary">Edit Post</a>
@endcan
@can('delete', $post)
<form method="POST" action="{{ route('posts.destroy', $post) }}">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Delete Post</button>
</form>
@endcan
How does this work?
- The
@candirective takes the ability name (which corresponds to your policy method, likeupdate) as the first argument. - The second argument is the model instance (
$post) the action is being performed on. - Laravel automatically finds the correct policy for the
Postmodel, calls theupdatemethod with the current user and the$post, and renders the content only if the method returnstrue.
The official documentation provides a complete reference for these directives, including the inverse @cannot and the useful @canany for checking multiple permissions.
Read the section 'Via Blade Templates'. This will reinforce what you just saw and introduce you to the full set of available directives.
2. Authorizing Actions in Controllers
Hiding a button in the UI is crucial for user experience, but it's not a security measure. A malicious user could still attempt to access the URL directly (e.g., /posts/1/edit). Therefore, you must perform authorization checks within your controllers as well.
Laravel offers several flexible ways to do this. We'll explore them in order from the most explicit to the most concise and idiomatic.
Laravel Roles and Permissions: Gates and Policies ...
This article from Laravel News provides an excellent overview of the different ways to check permissions. It's a great practical summary.
Read the section 'Various Ways to Check Gate Permission'. While it says 'Gate', these methods work seamlessly with Policies as well. Pay attention to the five options presented, as we will be discussing them.
Let's break down the most important methods for use inside a controller.
Method 1: The Manual Check with $user->can()
You can directly use the can() method on the user model. This is very explicit and easy to read. It returns a boolean, so you have to handle the "unauthorized" case yourself, usually by aborting with a 403 (Forbidden) status code.
// In PostController.php
public function update(Request $request, Post $post)
{
// Check if the current user CANNOT 'update' this specific post
if ($request->user()->cannot('update', $post)) {
abort(403);
}
// ... validation and update logic continues here
}
Method 2: The authorize() Helper (Recommended)
While the manual check works, it's a bit verbose. Laravel controllers include an AuthorizesRequests trait that provides a more convenient authorize method.
This is often the preferred method for checks inside a controller action.
// In PostController.php
public function update(Request $request, Post $post)
{
// If authorization fails, this method automatically
// throws an AuthorizationException, which results in a 403 HTTP response.
$this->authorize('update', $post);
// ... validation and update logic continues here
}
This single line achieves the same result as the if block and abort(403) call. It's cleaner and expresses the intent more clearly: "this action requires authorization".
The Laracasts video you watched earlier demonstrates a similar flow, using Gate::authorize() which functions almost identically.
3. Centralizing Authorization Checks
Placing authorization logic at the top of every controller method works, but we can do even better. Laravel encourages separating concerns, and authorization is no exception.
Option A: Using a Form Request
You are already familiar with using Form Requests to handle validation. They also contain an authorize() method, which is the perfect place to put your authorization logic. The check runs before the validation rules are checked and before your controller method is even executed.
-
Create a Form Request:
php artisan make:request UpdatePostRequest -
Add your logic to the
authorize()method:app/Http/Requests/UpdatePostRequest.php<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class UpdatePostRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { // 'post' is a route parameter name, e.g., /posts/{post} $post = $this->route('post'); return $this->user()->can('update', $post); } /** * Get the validation rules that apply to the request. */ public function rules(): array { return [ 'title' => 'required|max:255', 'body' => 'required', ]; } } -
Type-hint the request in your controller:
app/Http/Controllers/PostController.phpuse App\Http\Requests\UpdatePostRequest; public function update(UpdatePostRequest $request, Post $post) { // No authorization or validation check is needed here! // It has already passed. $post->update($request->validated()); return redirect()->route('posts.show', $post); }Your controller is now incredibly clean. Its only job is to perform the action, knowing the request has already been authorized and validated.
Option B: Using Middleware in Your Routes
Another powerful approach is to apply authorization directly at the routing level. This is especially useful if you're not using a Form Request for a particular action. Laravel provides the can middleware for this purpose.
// in routes/web.php
use App\Http\Controllers\PostController;
// ... other routes
// The 'can' middleware will check the 'update' policy for the 'post' model.
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])->middleware('can:update,post');
Route::put('/posts/{post}', [PostController::class, 'update'])->middleware('can:update,post');
// You can also use the convenient 'can' method on the route definition:
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->can('delete', 'post');
This approach makes your routes/web.php file very expressive. You can see at a glance not only the URI and controller action but also the permissions required to access it.
30 Days to Learn Laravel, Ep 23 - 6 Steps to Authorization Mastery
The Laracasts video gives a great walkthrough of moving authorization from the controller to the route level using middleware. This is a powerful technique for keeping controllers lean.
Watch the segment from 11:57 to 16:42. Notice how applying the can middleware allows the explicit authorization check to be removed from the controller method.
A Recommended Workflow
With so many options, what's the best practice? Here is a solid, conventional workflow for a standard CRUD application:
- Define a Policy: Always start by creating a dedicated policy for your model (e.g.,
PostPolicy). This centralizes all your authorization rules. - Use
@canin Blade: Use the@canand@cannotdirectives to conditionally render UI elements like buttons and links. This provides a good user experience. - Protect Your Routes:
- For
storeandupdateactions that require validation, use a Form Request and place your authorization logic in itsauthorize()method. - For other actions like
destroy,show, oredit, you have a choice:- Use
$this->authorize()at the top of the controller method. This is simple and effective. - Use the
canmiddleware in your routes file. This keeps controllers cleaner and makes your routes file more descriptive.
- Use
- For
This layered approach ensures your application is secure at every level, from the UI down to the core logic.
Conclusion
Congratulations! You've now mastered the final piece of Laravel's core authorization system. You know not only how to define permissions with Gates and Policies but also how to apply them effectively in your Blade views and controllers.
Key Takeaways:
- UI Authorization: Use the
@canand@cannotBlade directives to show or hide UI elements based on user permissions. - Controller Authorization: Always protect your controller actions.
$this->authorize('ability', $model)is the most direct and common method inside a controller.- Form Requests are excellent for combining authorization and validation for
store/updateactions. - Route Middleware (
->can(...)) is a clean way to protect routes before they even hit the controller.
- Layered Security: A robust application uses both UI and controller-level authorization.
Up Next:
We are now wrapping up our module on Authentication and Authorization. In the next lesson, we will shift gears and explore a powerful feature for improving your application's performance and responsiveness: Background Processing with Queues. You'll learn how to offload long-running tasks, like sending emails or processing large file uploads, to a background worker so your users don't have to wait.