Using Laravel Policy with middleware to protect routes
Tags: laravel, security
Published: 2023-08-26 11:11:32

So my goal was to only allow the logged in user to view or make changes to data that belongs to him. How do I know which data belongs to him? In the model `LanguagePack` I save his user id. Any other data like the `WordList` model has a relation to language pack. 

A user accesses the data via an url like this:

```
/languagepack/wordlist/1
```
So I don't want him to be able to change the id to 2 if that language pack wasn't created by him and then see and edit that data.

To do this I created a [policy class](https://laravel.com/docs/10.x/authorization#policy-methods) that looks like this:

```
<?php

namespace App\Policies;

use App\Models\LanguagePack;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class LanguagePackPolicy
{
    use HandlesAuthorization;

    public function view(User $user, LanguagePack $languagePack)
    {
        return $user->id === $languagePack->userid;
    }    

    public function update(User $user, LanguagePack $languagePack)
    {
        return $user->id === $languagePack->userid;
    }    

    public function delete(User $user, LanguagePack $languagePack)
    {
        return $user->id === $languagePack->user_id;
    }    
}
```

Then I created a middleware for getting the language pack model from the route and running the policy check by [authorizing actions](https://laravel.com/docs/10.x/authorization#via-middleware). If the action is allowed, it will continue the request, otherwise it throws a 403 error and aborts the request.
```
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class AuthorizeLanguagePack
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $languagePack = $request->route('languagePack'); 
        
        if ($languagePack) {
            $user = $request->user();
            
            if ($user->can('view', $languagePack) ||
                $user->can('update', $languagePack) ||
                $user->can('delete', $languagePack)) {
                return $next($request);
            }

            abort(403, 'Unauthorized');
        }       
        
        return $next($request);
    }
}
```
The middleware has to be registered in the file `src/app/Http/Kernel.php` like this:
```
protected $routeMiddleware = [
     //add to the list of route middlewares
     'authorize.languagepack' => \App\Http\Middleware\AuthorizeLanguagePack::class,
    ];
```

And finally we add the middleware key `authorize.languagepack` to the routes in `web.php`:
```
Route::middleware(['auth', 'authorize.languagepack'])->group(function () {
 Route::get('/dashboard', [DashboardController::class, 'index'])->name('home');    
    Route::get('languagepack/create', [LanguageInfoController::class, 'create']);
    Route::get('languagepack/edit/{languagePack}', [LanguageInfoController::class, 'edit']);
    Route::get('languagepack/wordlist/{languagePack}', [WordlistController::class, 'edit']);
})
```

There are probably other ways to achieve the same result. Let me know in the comments if you know of a better way...