Laravel 5 : multi-model middleware "Owner"

1.2k views Asked by At

I want to create a middleware to check if the authenticated user is the owner of the item. For a single model, the code is easy and would look something like :

    <?php namespace App\Http\Middleware;

    use Closure;

    class Owner {

        public function handle($request, Closure $next)
        {
            $id = $request->segments()[1]; //get the id parameter
            $model = Model::find($id); // find the model from DB

            if(!$item)
              return 'not found'; //if item not found

            //check if authenticated user is owner
            if($item->user_id == \Auth::id()){ 
              return $next($request);
            }else{
              return 'not allowed';
            }
        }
    }

Now let's say I have multiple models (ModelX, ModelY, ModelZ) and I don't want to rewrite the middleware handle function several times. How would one inject the referenced model in the middleware to make it accomodate multiple models from my app?

2

There are 2 answers

1
Gluten On

You could use route and middleware parameters:

Here’s the middleware (app/Http/Middleware/AbortIfNotOwner.php):

<?php

namespace App\Http\Middleware;

use Closure;

class AbortIfNotOwner
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string    $resourceName
     * @return mixed
     */
    public function handle($request, Closure $next, $resourceName)
    {
        $resourceId = $request->route()->parameter($resourceName);

        $userId = \DB::table($resourceName)->find($resourceId)->user_id;

        if ($request->user()->id != $userId) {
            abort(403, 'Unauthorized action.');
        }

        return $next($request);
    }
}

Inside app\Http\Kernel.php:

protected $routeMiddleware = [
     'owner' => 'App\Http\Middleware\AbortIfNotOwner',
];

Inside your route file (app/Http/routes.php):

Route::group(['middleware' => ['owner:articles']], function() {
    // your route
});

And optionally call it in the controller:

public function __construct()
{
    $this->middleware('owner:articles', ['only' => ['edit', 'update']]);
}
0
Cor Bosman On

You could use middleware parameters with route/model binding. Lets assume you have a post resource.

You would define the route. The middleware gets a parameter describing the resource you want to check the ownership of.

Route::get('/post/{post}', ['middleware' => 'owner:post', 'as' => 'post', 'uses' => 'PostController@showPost']);

Then you would bind your model in RouteServiceProvider.php:

$router->bind('post', function ($value) {
   return Post::where('id', $value)->firstOrFail();
});

Finally you would make the middleware. Important to know is that the binding becomes available in the request object as $request->post. Since you're passing the name of the resource to the middleware, it becomes the third parameter in the handle method.

public function handle($request, Closure $next, $resource)
{
    if ($request->{$resource}->user_id == \Auth::id()) {
      return $next($request);
    }
    return response('not allowed.', 401);
}

I suppose you could also programmatically find the resource name, but using this method your resource name and your route can be different.