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

michapietsch's avatar

Subsequent tests to API with token guard do not require Auth header

I'm testing an API with a token guard. When I make a request (this->getJson()), I provide the api token as a Bearer token in the Authorization header. Subsequent requests without the header also pass as authorized.

Isn't token authorization meant to be stateless? Even $this->flushHeaders() does not change the behavior. From a request without Authorization header I would expect to return a 401, not a 200. But it seems like the header on the first request is "re-used".

Could anybody please explain this behavior? Am I getting something wrong here?

0 likes
13 replies
michapietsch's avatar

Since the TokenGuard is not stateful, it has no logout() method. Still, Auth::check() and Auth::user() return true, or a user, respectively.

So the question basically is: Why isn't the authenticated state reversed after the request returns a response? Why is the user still authenticated via TokenGuard throughout the next test steps?

Is there any way to revoke the authentication manually in the test?

michapietsch's avatar

Ok, so up until now the solution has to be to split tests further to avoid this issue.

bugsysha's avatar

@michapietsch So everything is on default and you've just added api_token to users table and use that for auth in headers as described?

bugsysha's avatar

@michapietsch

I never wrote tests in that way. Have you tried reversing them? First unauthorized then authorized request? What you also need to think of is that on every request framework is "rebooted". So that is why you need to split tests. You should not test too much in a single test.

michapietsch's avatar

@bugsysha Yeah, if there the two possibilities, it works. But not with more than one authenticated user (restricted, manager, admin...)

I understand what you say about the "reboot". I just wonder, that the request in the test is not encapsultated in such a way. I would not have expected a user to be logged in after I get a response from a stateless request.

Yes, I split them up. Was just trying not to clutter the tests to much and put it in one "only authorized users can..."

bugsysha's avatar

@michapietsch

But not with more than one authenticated user (restricted, manager, admin...)

Then use @dataProvider nameOfTheMethod annotation for your test and just pass user type as argument.

I would not have expected a user to be logged in after I get a response from a stateless request.

It probably is just not separated for tests. For example if you have a session based auth then it would make sense for a user to remain logged in.

michapietsch's avatar

@bugsysha No, I meant, I wanted to make several requests with different users who have different access levels. But thanks for the hint!

For sessions it also makes sense for me to. But not in this case with the stateless TokenGuard. I can only guess it's to enable the possibility to make assertions about the authorization.

litvinjuan's avatar

I see you solved this by splitting the tests. I'm trying to test the logout behaviour of Sanctum (api auth). This means that I cannot split my test. My test requires to test this behaviour that seems to be unexpected.

My test does the following:

public function testCanLogout()
    {
        /** @var User $user */
        $user = factory(User::class)->create();
        $loginResponse = $this->postJson(route('auth.login'), ['email' => $user->email, 'password' => 'secret']);
        $token = $loginResponse['token'];

        $authenticatedResponse = $this->getJson(route('auth.current'), ['Authorization' => "Bearer $token"]);
        $authenticatedResponse->assertSuccessful();
        $authenticatedResponse->assertJsonPath('id', $user->id);

        $logoutResponse = $this->postJson(route('auth.logout'), [], ['Authorization' => "Bearer $token"]);
        $logoutResponse->assertSuccessful();

        $unauthenticatedResponse = $this->getJson(route('auth.current'), ['Authorization' => "Bearer $token"]);
        $unauthenticatedResponse->assertStatus(401);
    }

Basically, login -> current user -> logout -> current user

The last request does not fail, it returns 200 instead. After digging a bit, it seems that, as you said, it's not behaving in a stateless manner and thus cannot really test anything.

Does anyone have any idea how to "reset" the authenticated user in the middle of the test?

chhatrachhorm's avatar

I also face the exact same issue. Has anyone at least found any workaround? It would be really helpful.

bugsysha's avatar

@chhatrachhorm what @litvinjuan is doing is wrong. He has like 3-4 tests combined into one. You can definitely split it. You should test login route and just assert that you were redirected to the correct route which is protected with auth guard. For logout just assert that the request redirected you back to logout page. And so on...

Please or to participate in this conversation.