Module 6: The Finishing Touches

Editing, Deleting, and Authorization with Policies.

Controlling Who Can Do What

Welcome to the final module of our foundation series! Your application now has users who can create and view posts. But what's missing? The ability to edit and delete those posts. More importantly, we need to ensure that users can only edit or delete their own posts. This concept is called Authorization—it's different from Authentication (which confirms who you are). Authorization determines what you're allowed to do.

In this module, we'll implement the final pieces of our CRUD functionality (Update and Delete) and secure them using Laravel's elegant Policies.


1Authorization with Policies

Laravel Policies are simple PHP classes that group authorization logic for a specific model. A `PostPolicy` will contain all the rules for what users can do with posts.

1. Create the Policy

Let's generate a policy for our `Post` model using Artisan:

php artisan make:policy PostPolicy --model=Post

This creates a new file at `app/Policies/PostPolicy.php` with boilerplate methods for common actions (`view`, `create`, `update`, `delete`, etc.).

2. Define the Authorization Logic

We want to allow a user to update or delete a post only if their ID matches the `user_id` on the post. Let's add this logic to the `update` and `delete` methods in our `PostPolicy`:

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

/**
 * Determine whether the user can update the model.
 */
public function update(User $user, Post $post): bool
{
    return $user->id === $post->user_id;
}

/**
 * Determine whether the user can delete the model.
 */
public function delete(User $user, Post $post): bool
{
    return $user->id === $post->user_id;
}

Each method receives the currently authenticated user (`$user`) and the model instance (`$post`). It returns `true` if the action is allowed, and `false` otherwise.

3. Register the Policy

For Laravel to know about this policy, we must register it. Open `app/Providers/AuthServiceProvider.php` and add your model and policy to the `$policies` array:

use App\Models\Post;
use App\Policies\PostPolicy;

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

2Implementing Edit and Update Functionality

This is a two-step process: showing a form with the existing data (Edit), and then processing the submission of that form (Update).

1. The Routes

Let's define the routes in `routes/web.php`. We'll use route model binding, where Laravel automatically finds the `Post` from the ID in the URL.

// Show the form to edit a post
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])->middleware('auth');

// Update the post in the database
Route::patch('/posts/{post}', [PostController::class, 'update'])->middleware('auth');

2. The Controller Logic

Add the `edit` and `update` methods to `PostController`. Crucially, we'll use the `$this->authorize()` method to check our policy before proceeding.

public function edit(Post $post)
{
    $this->authorize('update', $post); // Check the policy!
    return view('posts.edit', ['post' => $post]);
}

public function update(Request $request, Post $post)
{
    $this->authorize('update', $post); // Check the policy!

    $validatedData = $request->validate([ 'title' => 'required|max:255', 'content' => 'required' ]);

    $post->update($validatedData);

    return redirect('/posts');
}

If `$this->authorize()` returns `false`, Laravel will automatically throw a 403 Forbidden HTTP exception, protecting your data.


3Implementing Delete and Updating the UI

Finally, let's add the delete functionality and show the Edit/Delete buttons only to authorized users.

1. The Delete Route and Controller Method

In `routes/web.php`, add the delete route:

Route::delete('/posts/{post}', [PostController::class, 'destroy'])->middleware('auth');

In `PostController`, add the `destroy` method:

public function destroy(Post $post)
{
    $this->authorize('delete', $post); // Check the policy!

    $post->delete();

    return redirect('/posts');
}

2. Conditionally Showing Buttons in Blade

We don't want every user to see "Edit" and "Delete" buttons on every post. We can use the `@can` Blade directive to check our policy directly in the view. Let's update `posts/index.blade.php`:

@foreach ($posts as $post)
    ...
    <p>By: {{ $post->user->name }}</p>

    @can('update', $post)
        <a href="/posts/{{ $post->id }}/edit">Edit</a>
    @endcan

    @can('delete', $post)
        <form method="POST" action="/posts/{{ $post->id }}">
            @csrf
            @method('DELETE')
            <button type="submit">Delete</button>
        </form>
    @endcan
    <hr>
@endforeach

Now, the Edit and Delete buttons will only render in the HTML if the logged-in user is the author of the post. The `@method('DELETE')` directive is used because HTML forms don't natively support DELETE requests.