The test
/**
* A user is able to logout and destroy their session.
*/
public function testUserCanLogout()
{
// Arrange
$user = factory('App\Models\User')->create();
$headers = ['AUTHORIZATION' => 'Bearer ' . \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user)];
// Assert
$this->get('api/auth/logout', $headers)
->seeStatusCode(202)
->seeHeader('Authorization', '');
// Token should be blacklisted
$this->get('api/me', $headers)
->seeStatusCode(500); // expects a 500 but actually returns 200 with user info
}
The logout endpoint
public function logout(Request $request) {
$user = $this->auth()->user();
JWTAuth::invalidate(JWTAuth::getToken());
return $this->response()->accepted()->header('Authorization', '');
}
However, the api/me endpoint actually returns the user linked to the token, but this does not happen when I execute these calls manually using Postman.
When I do $this->get('api/me', $headers)->dump(); instead, it shows me the below instead of giving a TokenBlacklistedException exception:
{#2034
+"data": {#2028
+"id": "1897"
+"username": "bernard73"
+"email": "[email protected]"
+"first_name": null
+"last_name": null
+"thumbnail": ""
+"reputation": 0
}
}
Any idea what I'm doing wrong?
Quick questions that might help answer:
api/me?invalidate?Thanks for your amazingly swift reply! I appreciate it tremendously.
"tymon/jwt-auth": "^0.5.5". api/me endpoint is covered by the api.auth and jwt.refresh middleware. It's worth noting I use the dingo/api package with jwt-auth integration (set it up according to the installation instructions).logout that handles the invalidate method in the first post. Hi,
I have exact same problem. Did you get it working? @mirague
@mikkokut I never did get it working :(
Ping @tdhsmith
Sorry! Some other questions:
Then try seeing if the token is actually being blacklisted:
public function testUserCanLogout()
{
// Arrange
$user = factory('App\Models\User')->create();
$token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user);
$payload = \Tymon\JWTAuth\Facades\JWTAuth::getPayload($token);
$headers = ['AUTHORIZATION' => 'Bearer ' . $token];
// Assert
$this->get('api/auth/logout', $headers)
->seeStatusCode(202)
->seeHeader('Authorization', '');
// Verify on the back-end that the token is blacklisted
$this->assertTrue(\Tymon\JWTAuth\Facades\JWTAuth::getBlacklist()->has($payload));
// Token should be blacklisted
$this->get('api/me', $headers)
->seeStatusCode(500); // expects a 500 but actually returns 200 with user info
}
If that assertion fails, then the problem is with the logout/blacklist-adding. If it passes, the problem is with the route/middleware/blacklist-checking.
Also I don't think it's affecting this test, but _you shouldn't use both jwt.auth and jwt.refresh on the same route_. It won't have the result you think.
I tested that and the token will be blacklisted successfully. The last api request which needs authentication still returns 200 after logout even it should not. With external api tester logout works well.
Derp. I'm pretty sure this is the same issue I describe here.
In general, Laravel+PHPUnit isn't very compatible with sending multiple requests in a single test. The application does not create new
Requestobjects (among other things) because the testing environment handles Laravel's IoC container different than other environments do.
Sorry I didn't think of that earlier!
So you can probably get by with an inserted $this->refreshApplication(), but to me that's an unclean solution. I'd advocate for splitting the test:
public function testUserLogoutBlacklistsToken()
{
// Arrange
$user = factory('App\Models\User')->create();
$token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user);
$payload = \Tymon\JWTAuth\Facades\JWTAuth::getPayload($token);
$headers = ['AUTHORIZATION' => 'Bearer ' . $token];
// Assert
$this->get('api/auth/logout', $headers)
->seeStatusCode(202)
->seeHeader('Authorization', '');
// Verify on the back-end that the token is blacklisted
$this->assertTrue(\Tymon\JWTAuth\Facades\JWTAuth::getBlacklist()->has($payload));
}
public function testAccessDeniedWithBlacklistedToken()
{
// Arrange
$user = factory('App\Models\User')->create();
$token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user);
\Tymon\JWTAuth\Facades\JWTAuth::invalidate($token);
// Sanity check that JWTAuth::invalidate worked
$this->assertTrue(\Tymon\JWTAuth\Facades\JWTAuth::getBlacklist()->has($payload));
// User data should not be returned and response should have HTTP 500
$this->get('api/me', $headers)
->seeStatusCode(500);
}
I think @tdhsmith has this resolved :+1:
btw @tdhsmith Thanks so much for your impeccable issue solving skills over the last few weeks. I've been mad busy, but slacking nonetheless :clap:
That works. Thanks!
@tymondesigns No problemo! Responding to issues is my way of procrastinating on other stuff lately.
Thanks a bunch @tymondesigns @tdhsmith !
The @tdhsmith solution doesn't worked that well for me but it out me in the right path. I tried in Laravel [5.7] to test and endpoint for my API but I had to modify it slightly.
If anyone else is having trouble setting this up, try the below configuration to test if the token is invalidated:
/**
* @test
* Test for: a User cannot make authorized calls after he logs out of the system.
*/
public function a_user_cannot_make_authorized_calls_after_he_logs_out_of_the_system()
{
// Given an invalidated token
$user = factory('App\User')->create();
$token = \JWTAuth::fromUser($user);
\JWTAuth::setToken($token)->invalidate();
// When the user tries to make an authorized call
$headers = ['Authorization' => 'Bearer ' . $token, 'Accept' => 'Application/json'];
$response = $this->get('/api/v1/profile', $headers);
// Then the user cannot complete the request
$response->assertStatus(401);
}
Sorry! Some other questions:
- What cache driver are you using in your test environment?
- Are you modifying time at all in your unit tests?
Then try seeing if the token is actually being blacklisted:
public function testUserCanLogout() { // Arrange $user = factory('App\Models\User')->create(); $token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user); $payload = \Tymon\JWTAuth\Facades\JWTAuth::getPayload($token); $headers = ['AUTHORIZATION' => 'Bearer ' . $token]; // Assert $this->get('api/auth/logout', $headers) ->seeStatusCode(202) ->seeHeader('Authorization', ''); // Verify on the back-end that the token is blacklisted $this->assertTrue(\Tymon\JWTAuth\Facades\JWTAuth::getBlacklist()->has($payload)); // Token should be blacklisted $this->get('api/me', $headers) ->seeStatusCode(500); // expects a 500 but actually returns 200 with user info }If that assertion fails, then the problem is with the logout/blacklist-adding. If it passes, the problem is with the route/middleware/blacklist-checking.
Also I don't think it's affecting this test, but _you shouldn't use both
jwt.authandjwt.refreshon the same route_. It won't have the result you think.
hey. I just simply change my cache driver from array to file then it works.
Most helpful comment
Derp. I'm pretty sure this is the same issue I describe here.
Sorry I didn't think of that earlier!
So you can probably get by with an inserted
$this->refreshApplication(), but to me that's an unclean solution. I'd advocate for splitting the test: