Home > Software engineering >  Laravel , it seems the middleware is not applied immediatly
Laravel , it seems the middleware is not applied immediatly

Time:05-23

I use Laravel 9.

I have this route , included in a group and middleware :

Route::middleware(['auth', 'ensureUserIsAdmin'])->group(function () {
    .....
    Route::resource('sports', SportController::class);
    .....
});

The middleware is only to check if the user is or not an administrator.

When the connected user is not admin, and tries to go to this route for a known sport

/sports/FOOT/edit

then he receives the response "FORBIDDEN". Perfect, the middleware made his job.

But when the same not admin user tries to go to a route for an unknown sport

/sports/UNKNOWSPORT/edit

then he receives the response "NOT FOUND". Is it normal ? It looks like the framework makes a database request and only after he applies the middleware.

What's wrong in my code ?

CodePudding user response:

Every route within web.php file is processed by web group middleware - you may find this in RouteServiceProvider class

Route::middleware('web')
        ->group(base_path('routes/web.php'));

This group defined within Kernel class and contains some app middleware. They're handled before any route specific middleware fires

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

So when you think your route has 'auth', 'ensureUserIsAdmin' as middleware only it is not quite true

In your case \Illuminate\Routing\Middleware\SubstituteBindings::class middleware is the one is failing and showing 404. And yes, it makes query to database if there are some route parameters in it

For simplicity, let say you have this route and only model with id=1 exists

Route::get('/sports/{sport}', function (Sport $sport) {
    dump($sport);
});

/sports/1 is dumping model, sports/2 shows 404 as expected. Let's comment \Illuminate\Routing\Middleware\SubstituteBindings::class

Now both pages are actually dumping model, but sports/2 shows empty default model with no parameters

If you need to change this logic and show 403 for non-existing models, you may add middleware to a group BEFORE \Illuminate\Routing\Middleware\SubstituteBindings::class middleware

For example, lets' create simple middleware which always return 403 like

public function handle(Request $request, Closure $next)
{
    abort(403);
}

And change web group to

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        AlwaysFalse::class, // HERE I AM
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

Now no matter what both pages will show 403

But this will be applied for EVERY route within routes/web.php file, so you may change the logic like

public function handle(Request $request, Closure $next)
{
    // Applied only in any route named `sports.something` - resource routes including
    // is_admin() function doesn't really exists, middleware logic is up to you
    abort_if((Route::is('sports.*') && !is_admin()), 403);

    return $next($request);
}

Now for admin user /sports/1 shows model, /sports/2 - 404 response. For non-admin user both pages will return 403.

About is it normal or not - I think yes. Ask yourself - what can you do (what your access level) over a thing which is doesn't even exists? So it better define first does model really exists and after you're sure it is, make something with it. But it is just my opinion, someone may disagree

Hope it'll help

CodePudding user response:

I don't know how your middleware is build but i normally do it like this.

in User Model

    public function isAdmin(){
    foreach($this->roles as $role){
        if($role->name == 'administrator' && $this->is_active == 1 ){
            return true;
        }
    }
}

in Middleware

    public function handle(Request $request, Closure $next)
{
    if(Auth::check()){
        if(Auth::user()->isAdmin()){
            return $next($request);
        }
    }
    return redirect('/');

}
  • Related