maximos's avatar

Middleware can:edit,place does not work properly

class PlacePolicy
{
    public function edit(User $user, Place $place): bool
    {
        return true;
    }
}

class PlaceController extends Controller
{
    public function edit(Place $place): Place
    {
        return $place;
    }
}

//api.php
Route::get('places/{place}', [PlaceController::class, 'edit'])->middleware('auth:sanctum', 'can:edit,place');

I get a 403 access error. If I remove can:edit,place, everything works. Most likely the problem is that the authorization auth:sanctum passes, all is ok. But next middleware (can:edit,place) does not run, because $user is null =\

0 likes
22 replies
Glukinho's avatar

Try passing array to ->middleware(), not two parameters:

Route::get('places/{place}', [PlaceController::class, 'edit'])
	->middleware( [ 'auth:sanctum', 'can:edit,place' ] );
JussiMannisto's avatar

Most likely the problem is that the authorization auth:sanctum passes, all is ok. But next middleware (can:edit,place) does not run, because $user is null =\

If auth:sanctum passes, then $user won't be null.

Is PlacePolicy registered? Laravel automatically registers policies if you're using the default directories and namespaces for everything, i.e. App\Models\Place and App\Policies\PlacePolicy. If not, you have to manually register the policy:

use App\Policies\PlacePolicy;
use Illuminate\Database\Eloquent\Attributes\UsePolicy;

#[UsePolicy(PlacePolicy::class)]
class Place extends Model {
	...
}

https://laravel.com/docs/12.x/authorization#registering-policies

maximos's avatar

Yes, i am using the default directories and namespaces. But even if i add #[UsePolicy(PlacePolicy::class)] to the my model Place, still dont work =( If i remove can:edit,place, then got no 403 errors.

maximos's avatar

By the way, it might be important. This is my api.php:

Route::prefix('v1')->group(function () {
    Route::prefix('places')->name('places.')->group(function() {
        // ... some other routes

        Route::get('{place}', [PlaceController::class, 'edit'])->middleware('auth:sanctum', 'can:edit,place')->name('edit');
    });
});

Here is guard in sanctum.php:

return [
    'guard' => ['web'],
];

Here is my guard settings in my auth.php:

return [
    'defaults' => [
        'guard' => 'api',
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    'guards' => [
        'api' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],
];
maximos's avatar

I have changed everywhere default guard to web. Still cant use middleware. But i can use it only in FormRequest with authorize method.

maximos's avatar

By the way, if i remove auth:sanctum and in a policy sets User nullable, method edit is calls, but user is null.

class PlacePolicy
{
    public function edit(?User $user, Place $place): bool
    {
        //$user is null here

        return true;
    }
}

//api.php
Route::get('places/{place}', [PlaceController::class, 'edit'])->middleware('can:edit,place');
JussiMannisto's avatar

Why did you remove the auth:sanctum middleware to test that? What happens when both middlewares are present?

JussiMannisto's avatar

Also, what is the context? Is this end point a straight up API, or something used in a SPA?

maximos's avatar

I've been sitting here for several days now and can't figure out what's going on. That's why I'm trying out all sorts of different ways to check, even the ones that seem absurd at first :)

With two middlewares, what I indicated in the very first message happens. I get a 403 error, since the edit policy method isn't called (I'd venture to assume because $user comes with the value null even though auth:sanctum passes normally)

JussiMannisto's avatar

With two middlewares, what I indicated in the very first message happens. I get a 403 error, since the edit policy method isn't called (I'd venture to assume because $user comes with the value null even though auth:sanctum passes normally)

In the original message you didn't have a nullable $user in the policy method. Have you verified that it never gets called even when $user is nullable and both middlewares are present?

JussiMannisto's avatar

I use two separate repositories, ui on nuxt and api on laravel (different subdomains, but the same top-level domain)

Have you read through the SPA authentication docs of Sanctum? Have you configured the correct domains in config/sanctum.php and config/session.php?

maximos's avatar

Have you read through the SPA authentication docs of Sanctum? Have you configured the correct domains in config/sanctum.php and config/session.php?

Yes, otherwise auth:sanctum would also cause a CORS or CSRF error.

maximos's avatar

In the original message you didn't have a nullable $user in the policy method. Have you verified that it never gets called even when $user is nullable and both middlewares are present?

The policy method is called if only can:edit,place is specified and if ?User is specified. Otherwise, the method is not called.

JussiMannisto's avatar

Yes, otherwise auth:sanctum would also cause a CORS or CSRF error.

That's not quite correct. CORS is a red herring, as it's independent from Sanctum: an API request may be valid from a CORS standpoint even if sessions don't work. You also won't get a CSRF error here since you're calling a GET route, which aren't CSRF-protected.

The sanctum/csrf-cookie route uses the web middleware stack, so it'll work even if your API sessions don't. The login route might throw an error if API sessions are misconfigured, but that depends on how and where it's implemented.

To me it seems likely that Sanctum is misconfigured, or possibly something's wrong with requests sent from the UI. It's hard to guess what's wrong without seeing all of the relevant code and configuration. If I were you, I'd double-check Sanctum and session config, especially SANCTUM_STATEFUL_DOMAINS and SESSION_DOMAIN. Also make sure you've made the API routes stateful for Sanctum.

Another (unlikely) possibility is that the middlewares are run in the wrong order, but that could only happen if you've manually modified middleware priorities. Laravel's internal middleware priority list always runs authentication (auth:sanctum) before authorization (can).

1 like
maximos's avatar
maximos
OP
Best Answer
Level 6

@JussiMannisto Big thanks for trying to help, dude. I found problem. In my AppServiceProvider.php i returned true or false and next checks are not happened:

Gate::before(fn(User $user) => $user->hasRole(RoleEnum::OWNER));

I just fix it:

Gate::before(fn(User $user) => $user->hasRole(RoleEnum::OWNER) ? true : null);

I am happy :D

Please or to participate in this conversation.