0

I am using Laravel 9, extending Laravel Breeze for authentication. I have routes that are protected by the auth middleware (\App\Http\Middleware\Authenticate.php), in which I've specified that redirectTo() should return route('auth.login'). This was working great until a few days ago when suddenly all stopped! This was around the same time that I introduced Laravel Sanctum for API authentication.

When not logged in, and you try to access a protected route, this 500 error pops up:

Attempt to read property "headers" on bool at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:191

I have tried to debug this and found out that the issue is in the unauthenticated() method in Laravel's main Authenticate.php class. The redirection does not happen properly. When I extend this class in my app's Authenticate.php, I realise that the following works and I get a 404 error:

protected function unauthenticated($request, array $guards)
{
    abort(404);
}

If, however, I do the following, I am back to the original error detailed in the aforementioned link:

protected function unauthenticated($request, array $guards)
{
    return redirect(route('auth.login'));
}

I can't explain why this is the case. If I echo out the redirect, it also kind of works, but this is a hack.

Someone, please help explain what's going on.

miken32
  • 42,008
  • 16
  • 111
  • 154
kJamesy
  • 5,973
  • 5
  • 20
  • 22
  • The error (which should of course be included in the question) is pretty clear; at some point the method is passed a boolean instead of a response object. I'd put some debugging in at line 80 of `VerifyCsrfToken.php` and see what that closure is being passed. – miken32 Aug 19 '22 at 19:35
  • Yes, I had already done this. The response is `true`. `$next($request)` returns true, and the next middleware is `Authenticate` - which is where I think the problem is. – kJamesy Aug 21 '22 at 09:05
  • 1
    Well if the closure is being passed `true` instead of a response, just work your way backwards through the code until you find the problem. Every middleware should call `$next($request)` so the request pipeline can continue. – miken32 Aug 21 '22 at 14:52

1 Answers1

0

After more debugging, I found that the issue was indeed in the Authenticate middleware, and specifically in the unauthenticated() method. This throws the AuthenticationException which in turn is hijacked by a custom Handler I had added as part of the Laravel sanctum that I introduced, and forgotten about, for API authentication.

The purpose of this handler was to override the default Laravel error message when an API request wasn't authenticated.

In there, I check if the request was to the /api route and then return the custom error message. If, however, it wasn't, I was returning true. This is the culprit.

$this->renderable(function (AuthenticationException $e, $request) {
    if ($request->is('api/*')) {
        return response()->json(['error' => 'Not authorised.'], 401);
    }

    return true; //Bad
});

The correct way to do it (when I want Laravel to handle any other requests) is to simply return nothing. The docs say:

If the closure given to the renderable method does not return a value, Laravel's default exception rendering will be utilized

kJamesy
  • 5,973
  • 5
  • 20
  • 22