Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

JerryBels's avatar

Controller dependency is injected before middleware is executed

Hello guys!

So I created a middleware to limit the data a connected user has access to by adding global scopes depending on some informations:

public function handle(Request $request, Closure $next)
    {
          if (auth()->user()?->organization_id) {
              User::addGlobalScope(new OrganizationScope(auth()->user()->organization));
          }

        return $next($request);
    }

The middleware is added to the 'auth.group' middleware group in Kernel.php which is used in web.php:

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

Then in the controller, I would expect a user to get a 404 when trying to see a page of a user he has no rights to. But the $user is retrieved before the middleware applies the global scope!

public function show(User $user, Request $request) {
  // dd($user); // <= This actually contains the User model! It shouldn't, of course.
  // dd(User::find($user->id)); // <= null, as it should!
}

So, the dependency is apparently calculated before the middleware is applied. If I'm trying to move the middleware into the 'web' group in Kernel.php it's the same. And in the main $middleware array, the authenticated user's data is not available yet.

I found this discussion that seems to be on topic : https://github.com/laravel/framework/issues/44177 but the possible solutions (and Taylor's PR) seems to point to a solution in the controller itself. Not what I'm trying to do, or I can't see how to adapt it.

Before that I was applying the global scopes at the Model level, in the booted function (as shown in the docs). But I had lots of issues with that - namely, accessing a relationship from there to check what is allowed or not is problematic, as the relationship call will look for something in the Model itself, and said model is not ready (that's the point of the booted method, right...). For example, checking a relationship of the connected user on the User model has to be done with a direct query to the db, that will be ran every time the Model is called... Not good.

Anyway, I like the middleware approach as it is a clean way to deal with rights as well, I think. Any recommandation?

0 likes
2 replies
kreierson's avatar
Level 13

What does your OrganizationScope() look like?

I have something similar going on.

I created a listener that listens for an authenticated event: I have the below code in my EventServiceProvider.

protected $listen = [
        Authenticated::class => [
            RegisterTenantScope::class,
        ],
    ];

Then my RegisterTenantScope is my listener so this fires whenever someone is authenticated

public function handle($event)
    {
        session()->put('tenant_id', auth()->user()->current_tenant_id);

        $this->getModels()->each(function($model) {
            $model::addGlobalScope(new TenantScope);
            $model::creating(function ($model) {
                    if($model->getTable() == 'users') {
                        $model->current_tenant_id = session('tenant_id');
                    } elseif($model->getTable() !== 'tenants' && $model->getTable() !== 'roles') {
                        $model->tenant_id = session('tenant_id');
                    }
            });
        });
    }

The getModels() method essentially returns all my models in my models directory. This listener applies the global scope to all my models

This ensures the scope gets applied after someone is authenticated. I do have a middleware, but that is not where I am adding the global scope. The middleware just sets some variables based on the authenticated user as I am using Spatie Permission with the teams feature. https://spatie.be/docs/laravel-permission/v5/basic-usage/teams-permissions

1 like
JerryBels's avatar

@kreierson Interesting! You have your tenant_id in the session then. But I don't understand how the global scope is applied on subsequent navigations? I mean, sure, when the event is fired the listener does its job, but even with tenant_id being in the session, your $model::addGlobalScope(new TenantScope); is applied only for the current call, right? Do you have it somewhere else, like in the booted function of your models or something?

You wanted to see my OrganizationScope but honestly it doesn't do much and is unrelated to the issue:

    public function __construct(Organization $organization)
    {
        $this->organization = $organization;
    }
    public function apply(Builder $builder, Model $model)
    {
        $builder->where($this->organization->id, $this->organization->getKey());
    }

Please or to participate in this conversation.