Logo

dev-resources.site

for different kinds of informations.

Using Laravel Policy with middleware to protect routes

Published at
8/26/2023
Categories
laravel
security
Author
Philip Perry
Categories
2 categories in total
laravel
open
security
open
Using Laravel Policy with middleware to protect routes

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 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. 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...

Featured ones: