Framework: Unauthenticated error handling with json request

Created on 15 Sep 2018  Â·  13Comments  Â·  Source: laravel/framework

  • Laravel Version: 5.7
  • PHP Version: 7.1
  • Database Driver & Version: doesn't affect issue behavior

Description:

I have Laravel 5.7 app as API service. I'm trying to get json response while trying to access auth guarded route.

My route:

Route::group(['prefix' => 'auth'], function () {
    Route::get('me', 'AuthController@me');
});

method:

public function me()
{
    return auth()->user();
}

When I make get request to endpoint described above, I got 500 error:

"message": "Route [login] not defined.",
"exception": "InvalidArgumentException",

Because I use Laravel app as API only service, I don't have any web routes. Let's take a look on handling AuthenticationException:

protected function unauthenticated($request, AuthenticationException $exception)
{
    return $request->expectsJson()
                ? response()->json(['message' => $exception->getMessage()], 401)
                : redirect()->guest($exception->redirectTo() ?? route('login'));
}

If we send request with Accept: application/json header, we might get this response: response()->json(['message' => $exception->getMessage()], 401).

I'm using axios on client for making xhr requests, so Accept: application/json header is present. By the way, I don't get json response back.

expectsJson():

public function expectsJson()
{
    return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson();
}

Since my request has Accept: application/json header, $this->wantsJson() might be satisfied... What's wrong?

Most helpful comment

OK, I've looked into this and I'm pretty sure it has nothing to do with JSON or the handling of the AuthenticationException, because the AuthenticationException is never even thrown. Try dumping $request->expectsJson() - does this return true? It probably does.

This line is indeed the problem. It's called when the parent tries to construct the Exception:

throw new AuthenticationException(
    'Unauthenticated.', $guards, $this->redirectTo($request)
);

But the login route doesn't exist by default, so the route('login') throws an InvalidArgumentException.

This bug is present even in a fresh Laravel install:

laravel new auth-test
cd auth-test
php artisan serve

and then

curl -H "Accept: application/json" http://localhost:8000/api/user

and you will see the same Exception.

This bug seems to have been introduced in https://github.com/laravel/laravel/commit/a14e62325cbe82a615ccd2e80925c75cb0bf1eaf.

Workaround: Add a route named "login" or remove the call to route('login') from the Middleware.

All 13 comments

Could you share your application on GitHub?

@TBlindaruk yes, here it is

php artisan jwt:secret is required (I'm using jwt-auth)

@Tarasovych
https://github.com/Tarasovych/issue25636/blob/master/app/Http/Controllers/AuthController.php#L9

You did not have a login action within your controller,
Please change

$this->middleware('auth:api', ['except' => ['login']]);

to

$this->middleware('auth:api');

@TBlindaruk changed. This doesn't affect anything by the way.

Commenting (or removing) this line results correct response:

{
    "message": "Unauthenticated."
}

But I can't confirm if this is right solution.

Looks like 2 separate issues to me;

  1. $request->expectsJson() is returning false, so it was hitting your web route
  2. This was throwing an error 500, since no route('login') exists.

You'll need to investigate why $request->expectsJson() is returning false?

@laurencei about 1st issue: can I debug it somehow? Can custom FormRequest be suitable in my case? I guess, I can try isJson() to check whatis the type of request.
Totally agree with 2nd issue as it is an aftereffect of the 1st one.

Commenting (or removing) this line results correct response:

Instead of removing the line, replace it with dd($request); and check the headers.

@AegirLeet thank you!

So far:

  • looking through dd($request):
#json: null
...
#acceptableContentTypes: null
...
#headers: array:3 [â–¼
      "host" => array:1 [â–¶]
      "user-agent" => array:1 [â–¶]
      "accept" => array:1 [â–¼
        0 => "application/json"
      ]
    ]
  • dd($request->ajax()) returns false
  • dd($request->isJson()) returns false

Seems a bit strange for me... I thought this

      "accept" => array:1 [â–¼
        0 => "application/json"
      ]

is enough to satisfy json request.

OK, I've looked into this and I'm pretty sure it has nothing to do with JSON or the handling of the AuthenticationException, because the AuthenticationException is never even thrown. Try dumping $request->expectsJson() - does this return true? It probably does.

This line is indeed the problem. It's called when the parent tries to construct the Exception:

throw new AuthenticationException(
    'Unauthenticated.', $guards, $this->redirectTo($request)
);

But the login route doesn't exist by default, so the route('login') throws an InvalidArgumentException.

This bug is present even in a fresh Laravel install:

laravel new auth-test
cd auth-test
php artisan serve

and then

curl -H "Accept: application/json" http://localhost:8000/api/user

and you will see the same Exception.

This bug seems to have been introduced in https://github.com/laravel/laravel/commit/a14e62325cbe82a615ccd2e80925c75cb0bf1eaf.

Workaround: Add a route named "login" or remove the call to route('login') from the Middleware.

@AegirLeet
dd($request->expectsJson()) returns true

Going to remove route('login') call for now.

I'll be closing this, as Taylor has said in the PR that you are free to change this in your own application.

Let me know if there is another reason to keep this ticket open. Thanks.

You should use Accept header set to application/json in your request.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Anahkiasen picture Anahkiasen  Â·  3Comments

lzp819739483 picture lzp819739483  Â·  3Comments

Fuzzyma picture Fuzzyma  Â·  3Comments

PhiloNL picture PhiloNL  Â·  3Comments

shopblocks picture shopblocks  Â·  3Comments