Summer Sale! All accounts are 50% off this week.

manuelsteiner's avatar

Laravel testing with Auth::once() permanently authenticates user

Hello,

does anyone have experience with testing methods where they use Auth::once()? I have the following problem.

When I make a request in a test to a resource that makes use of Auth::once() and the authentication is successful, it seems the test context stores the authenticated state of the user permanently. This means in subsequent calls, assertions such as assertGuest() are always false. In the actual app, Auth::once() obviously only authenticates the user for the current request. The test context does not seem to honour that the authentication should only be valid for a single request.

I can get around the storage of auth status by manually using auth()->logout() in the test after the HTTP request to the resource but that is obviously not ideal since that is what the application does and it should not be required as part of the test.

A specific situation where this is a problem: I want to authenticate the user once to check on some user database fields and then continue as guest (e.g. for two factor authentication). In combination with the guest middleware to only access the two factor page as guest, this works in the real application but since the user stays authenticated after the call to Auth::once(), the two factor page is not reachable since the test context thinks the user is already fully authenticated.

Is there a way I can call some method to refresh the authentication status within a test or something similar?

Thanks for any help.

0 likes
6 replies
mabdullahsari's avatar

Your way of thinking is wrong, imo. If you'd like to become a guest again after authentication, then you will have to logout. It is not "wrong", you are testing the application itself after all.

However, I do think that you are putting too much stuff in one single test case. Why not simply put them in separate functions so you start with a clean state for each one?

manuelsteiner's avatar

I think I don't understand what you are implying.

Auth::once() is signing you in for a single request. That works fine in the application. I can not test the controller method making use of that function though since the test context does not respect the fact that Auth::once() is only authenticating the user for a single request and the user just keeps being authenticated. And that is wrongly reflected in the test context since it differs from how the Auth::once() function actually works.

That is exactly why I am using a feature test, to test the whole flow and make sure it works. It is just that the test context itself is not behaving like the implementation in a normal application. I do not think there is anything wrong with testing a controller method with a feature test.

I'll add an example once I have time to maybe clarify my situation.

manuelsteiner's avatar

Alright,

here is a trivial example. With a GET and a POST routes on /home to the HomeController, take into consideration the following HomeController class.

class HomeController extends Controller
{
    public function get()
    {
        return 'GET';
    }

    public function post(Request $request)
    {
        $credentials = [
            'email'    => $request->email,
            'password' => $request->password,
        ];

        if(Auth::once($credentials)) {
            return 'OK';
        }

        return 'NOT OK';
    }
}

and the following test class.

class ExampleTest extends TestCase
{
    public function test_auth_once()
    {
        $response = $this->post('/home', [
            'email' => '[email protected]',
            'password' => 's3cure-p4s5w0rd',
        ]);

        // theoretically the user should be a guest here already
        $response->assertStatus(200);
        $response->assertSee('OK');
        $this->assertGuest();  // assertion fails

        $response = $this->get('/home');

        // but definitely here after another extra call
        $response->assertStatus(200);
        $response->assertSee('GET');
        $this->assertGuest(); // assertion fails
    }
}

Now I hope it is clear what I mean.

After the POST request, the user should already be deauthenticated again but at the very latest after a separate GET request (maybe taking into account that the response for the first request would yield authenticated). The user just stays logged in during the whole test context and the assertGuest() assertions fail, which is not the correct behaviour for Auith::once(). Again, it works fine in the app itself. The test context just stores the authenticated state and does not change it.

antoine_l's avatar

Hello @manuelsteiner

I'm having the same problem. I use Auth::onceUsingId(...). By using it, I log a user into the application for a single request. No sessions or cookies is utilized.

However, in my test after making a request to a route that uses Auth::onceUsingId(), I'm still authenticated. It means I can make other requests that require authentication. But for these other requests, I should be a guest, not an authenticated user.

Have you found a solution since then?

Thanks!

rodrigoriome's avatar

I've run into this same problem. I was able to get the feature (user impersonation) working as expected, but I'm unable to test it properly.

Please or to participate in this conversation.