LBR
LBR

Reputation: 51

Middleware order issue in Laravel: CheckPasswordStatus executed before auth middleware resulting in 'Call to a member function can() on null' error

I'm facing an issue in Laravel related to the middleware after I updated to from Laravel 7 to 9 and Backpack from 4 to 5.

I should get redirected to the login page (example.com/admin/login) when I try to access a route I'm not allowed to without being logged in (like example.com/admin/contacts).

Instead I get an error Call to a member function can() on null which indicates, that my admin middleware CheckPasswordStatus is executed before the auth-middleware.

This is my middleware declaration in Kernel.php:

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,
        ],
        'admin' => [
            CheckPasswordStatus::class,
        ],
        'api' => [
            'throttle:5000,10',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.basic.source' => \App\Http\Middleware\AuthenticateWithBasicAuthForSources::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' => \App\Http\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];

I use it in my route declaration in routes/backpack/custom.php as follows:

Route::prefix(config('backpack.base.route_prefix', 'admin'))->middleware([backpack_middleware(), config('backpack.base.web_middleware', 'web')])->group(function () { // custom admin routes
    Route::crud('contact', ContactCrudController::class);
    Route::crud('conflicts', IncomingImportConflictCrudController::class);
    Route::patch('contacts/{id}/advertising-rejection', [ContactCrudController::class, 'advertisingRejection'])->name('contact.advertising_rejection');
    Route::post('conflicts/{id}/new', [IncomingImportConflictCrudController::class, 'creatNewContact'])->name('new-contact');
    Route::get('conflicts/{id}/compare/{compareId}', [IncomingImportConflictCrudController::class, 'compare']);
    });

In my config/backpack/base.php is the 'middleware_key' => 'admin'.

The middleware CheckPasswordStatus simply checks if a user's password has expired and if so, it redirects them to a password reset page:

public function handle($request, Closure $next)
    {
        if ($user = $request->user()) {
            if ($user->passwordIsExpired()) {
                return redirect()->route('password.reset.manually');
            }
        }

        return $next($request);
    }

When I use something as the middleware_key (which isn't defined in my middleware groups), it works as expected, leading to a redirection to the login screen. Shouldn't this throw another error? Maybe there is some default behavior.

When I set the middleware_key back to admin but comment out the admin middleware in Kernel.php, everything works fine. (except the CheckPasswordStatus::class-middleware)

When I use web as the middleware_key, I get the same error and no redirect.

I tried the two solutions from Laravel Middleware Error - Call to a member function isBasic() on null but if I use the authenticated method in 'CheckPasswordStatus::class' the error says, CheckPasswordStatus middleware not callable

If i use the Auth::check() as condition in the 'CheckPasswordStatus::class' I got an empty page instead of a redirect.

Upvotes: 4

Views: 107

Answers (2)

apokryfos
apokryfos

Reputation: 40690

You can try explicitly setting the middleware priority, if you are relying on a specific order of middleware execution. Add this in your Kernel.php:

protected $middlewarePriority = [
    \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
    \Illuminate\Cookie\Middleware\EncryptCookies::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
    \Illuminate\Routing\Middleware\ThrottleRequests::class,
    \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
    \Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \App\Http\Middleware\Authenticate::class,
    \Illuminate\Auth\Middleware\Authorize::class,
    CheckPasswordStatus::class
];

This should ensure CheckPasswordStatus runs after the authenticate and authorise middleware

Upvotes: 1

Pedro X
Pedro X

Reputation: 1071

I think you should be fine by adding your middleware to middleware_class https://github.com/Laravel-Backpack/CRUD/blob/1b67f8efdbaa48842e0d31995068b44b8fc5c66d/src/config/backpack/base.php#L266

You can get the authenticated user with: backpack_user() or backpack_auth()->user() or Auth::guard('admin')->user()(if admin is your guard name).

Cheers

Upvotes: 1

Related Questions