0

I am using Laravel Passport and have a logout function in the controller. See below:

public function logout(Request $request){
    auth()->user()->token()->revoke();
    $response = ['message' => 'You have been successfully logged out!'];
    return response($response, 200);
}

I am now trying to write a unit test for this but the user stays logged in even after the logout and the token being revoked. I have found this Method Illuminate\Auth\RequestGuard::logout does not exist Laravel Passport but even this solution does not work for me. I am guessing it might be because I am using Laravel 8. My Unit test looks like this:

public function testLogout()
{
    //Random email and password
    $email = $this->faker->email;
    $password = $this->faker->password(8);

    //Create a user
    $this->createUser($this->faker->name, $email, $password);

    //Data for the post request
    $data = [
        'email' => $email,
        'password' => $password
    ];

    //Try login
    $response = $this->json('POST','api/login', $data);
    //Assert it was successful
    $response->assertStatus(200);

    //Assert we received a token
    $this->assertArrayHasKey('token', $response->json());

    //Get the token
    $token = $response->json()['token'];

    //Setup authenticated header
    $header = [
        'Authorization' => 'Bearer '.$token
    ];
    //try to access authenticated route
    $response = $this->json('get', 'api/ads', [], $header);
    //Assert it was successful
    $response->assertStatus(200);

    $this->resetAuth();
    //Logout the user
    $response = $this->post('api/logout', [], $header);
    //Assert it was successful
    $response->assertStatus(200);

    //try to access authenticated route
    $response = $this->json('get', 'api/ads', [], $header);
    //Assert it returns unathorized error
    $response->assertStatus(401);


    //Delete the user
    User::where('email', $email)->delete();

}

And the result is the following:

Expected status code 401 but received 200. Failed asserting that 401 is identical to 200.

user3658609
  • 539
  • 2
  • 6
  • 14

2 Answers2

0

You are doing too much in a single test. But other than that, I couldn't really spot any issues in your code. But this worked for me:

class LogoutController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api');
    }

    public function logout()
    {
        Auth::user()->token()->revoke();
        $tokenId = Auth::user()->token()->id;

        $refreshTokenRepository = app('Laravel\Passport\RefreshTokenRepository');
        $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);

        return response(null, Response::HTTP_NO_CONTENT);
    }
}

And the test can simply do something like:

public function testAnAuthenticatedUserCanLogout()
{
    $user = User::factory()->create();

    Passport::actingAs($user);

    $this->postJson('/api/logout')
        ->assertNoContent();
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
P. K. Tharindu
  • 2,565
  • 3
  • 17
  • 34
-1

Your logout method looks strange. I would do this

use Illuminate\Support\Facades\Auth;

// ... other controller methods

public function logout(Request $request)
{
  Auth::logout();
  $request->session()->invalidate();
  $request->session()->regenerateToken();

  // ...do a redirect or other stuff
}

to logout out. This should properly logout the user. This is also the proposed way in the Laravel docs.

For logging out from Laravel Passport you can run

if (Auth::check()) {
  Auth::user()->token()->revoke();
}

to revoke the current token used. This will definitely log the user out from the current device where he requested to log out.

Make sure in your routes/web.php you have your logout route inside the group(['middleware' => 'auth:api'] group.

If you want to log out from all the devices where he's logged in, you can also remove the token from database.

codedge
  • 4,754
  • 2
  • 22
  • 38