Middleware to verify ownership

Watch the video course on Creating a Buy & Sell website using Laravel 8.

Intro: Well, our website looks good. But there is still a serious issue, once we fix this we will be ready for the initial release. Now before I show you how to fix the issue, let’s understand the issue itself. You’ll need more than one account for this, I have created two accounts on the website and I have added products using both accounts. You have to do the same. Now login with one account and try to edit the details of a product added by the other account. If you try to find the edit button on other’s products you won’t find it as we are verifying the ownership before displaying the edit and delete buttons. So to access the product edit form you have to manually enter the URL on the address bar of the browser http://bns.go/edit/5 . Enter the id of a product added by the other account, and you’ll get the form to edit the product, anyone can edit anyone else’s product, …. this is a serious issue. And in this video, I’ll show you how to fix this by creating our own middleware.

  1. First, we shall create our own middleware
  2. We shall assign a key to the middleware inside the Kernel.php file
  3. Finally, we shall apply this middleware to the route

So let’s get started.

Steps:

1. Create a middleware by running the command php artisan make:middleware VerifySeller Let’s name the middleware VerifySeller.

2. Now, let’s revise the concept of middleware. You remember, right! a middleware can intercept an HTTP request of the application, make a decision whether to let the request proceed or to deny it. Here in our case, whenever a user makes a request to edit a product using this middleware VerifySeller we shall intercept the request, check whether the user trying to edit the product (the logged-in user) is the owner of the product. If this user is the owner we shall allow the request to proceed to the next step… to display the product edit form, but if the user is not the owner we shall redirect him/her to the product details page instead. Currently, our middleware looks like this…

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerifySeller
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        return $next($request);
    }
}

3. We have to write the logic for making the decision, inside this function handle().
Currently, there is a single statement inside this function. This allows the request to proceed to the next level.
Now we shall add some code before this statement.

4. First of all, we have to get the product id from the route because we shall fetch the product from the database to know who is the owner/seller, from the value of the user_id column. We can call the function route() on the request object ( $request->route()) to get an instance of the class Route representing the current route and then if we call the function parameter() on it and pass the name of the route parameter we are interested in, we’ll get the value of that parameter. So lets pass ‘id’, as this is name of the parameter on the route /edit/{id}, and assign the result on a variable named product_id.
$product_id=$request->route()->parameter('id');
Now fetch the product by id, by calling the function find() on the Product model.
$product_id=$request->route()->parameter('id'); $product=Product::find($product_id);
But for this to work we must include the class Product inside this file, so at the top of this file add this line
use App\\Models\\Product;
Let’s also include the class Auth as we’ll need it next, to get the id of the logged-in user.
use Illuminate\Support\Facades\Auth;

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Product;

class VerifySeller
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $product_id=$request->route()->parameter('id');
        $product=Product::find($product_id);


        return $next($request);
    }
}

5. Now add an if statement to check if Auth::id() (which gives the id of the logged-in user) is not equal to $product→user_id (which is the user id of the seller or the owner).

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Product;

class VerifySeller
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $product_id=$request->route()->parameter('id');
        $product=Product::find($product_id);


        if(Auth::id()!=$product->user_id){
        }

        return $next($request);
    }
}

6. In the condition of the if statement we shall check if the two user ids are not equal. So if it is true, which means the user trying to edit the product is not the owner then we shall redirect the user to the details page for the product, otherwise, we shall allow the user to edit it. We shall call the function redirect and pass the URI for the details page to it.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\Product;

class VerifySeller
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $product_id=$request->route()->parameter('id');
        $product=Product::find($product_id);


        if(Auth::id()!=$product->user_id){
          return redirect("/product/$product_id");
        }

        return $next($request);
    }
}

7. So we have added the logic to the middleware, but to be able to apply it to the routes we have to assign a key to this middleware. For that open the file Kernel.php from the app/Http folder.

At the bottom of this file, you’ll find the array named $routeMiddleware, notice how the keys are assigned to the middlewares. Now we shall do the same with our middleware VerifySeller.

First, specify the key, let’s name it verify_seller , we shall use this name while adding the middleware to the routes, then add the fat-arrow and then the path of the class VerifySeller.
'verify_seller' => \App\Http\Middleware\VerifySeller::class,

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'verify_seller' => \App\Http\Middleware\VerifySeller::class,
    ];
}

8. Now, open the route file web.php and add the middleware to the route edit. We have to use an array to apply multiple middlewares to the route.
Route::get('/edit/{id}',[ProductsController::class,'edit'])->middleware(['auth','verify_seller']);

9. Now try to edit a product added by a different account than the one you are logged in with. Try manually entering the URL on the address bar like http://bns.go/edit/5 Try changing the id, you won’t be able to edit other’s products anymore.

Conclusion: Middleware in Laravel is awesome, right! I hope enjoyed creating your own middleware. Now you can use this middleware to protect any route which you don’t want a non-owner to be able to access. I’ll add it to the delete route too.

Leave a Reply

Your email address will not be published.