jwt-auth: Cant pass token on tests (also, please add how to test to the Wiki)
Hello amazing community. This week I got a new project and I decided to give this bundle a try. As a good dev should do, Im testing everything I can. But, unfortunately, I cant perform a basic test, and I dont know why (not sure if Im doing wrong or if its a problem with the bundle). Also, Id like to suggest to update the Wiki, adding a section for “testing best practices”, since we all need to know how to generate tokens and test our app 🔎
The problem
I`ve got a route for login and a protected route:
<?php
use Illuminate\Http\Response as HttpResponse;
// login doesnt need JWT authentication
Route::post('login', function () {
$credentials = Input::only('email', 'password');
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(false, HttpResponse::HTTP_UNAUTHORIZED);
}
return response()->json(compact('token'), HttpResponse::HTTP_ACCEPTED);
});
// all other admin routes needs to be verified for JWT Token
// When not logged in, Exceptions are raised and intercepted at App/Exceptions/Handler
Route::group(['before' => 'jwt-auth'], function () {
Route::get('/', function () {
$user = JWTAuth::parseToken()->toUser();
return response()->json(compact('user'));
});
});
And I wrote this simple test for it:
<?php
namespace Tests\App\Http;
use Illuminate\Http\Response as HttpResponse;
use JWTAuth;
/**
* Class AdminRoutesSecurityTest
* This class holds tests for validating security measures and JWT integration
* for ADMIN API routes.
*
* @package Tests\App\Http
*/
class AdminRoutesSecurityTest extends \TestCase
{
/**
* User may want to access the main admin page without authenticating.
* User should get access denied
*/
public function testGetMainUnauthenticated()
{
// as a user, I try to access the admin panels without a JWT token
$response = $this->call('GET', '/');
// I should be blocked
$this->assertEquals(HttpResponse::HTTP_UNAUTHORIZED, $response->status());
}
/**
* User may want to access the main admin page.
* For this, they will pass a JWT token
*/
public function testGetMainAuthenticated()
{
$credentials = JWTAuth::attempt(['email' => 'admin@app.com', 'password' => 'secret']);
// as a user, I try to access the admin panels without a JWT token
$response = $this->call(
'GET',
'/',
[], //parameters
[], //cookies
[], // files
['HTTP_Authorization' => 'Bearer ' . $credentials], // server
[]
);
// I should be accepted
$this->assertEquals(HttpResponse::HTTP_OK, $response->status());
}
/**
* User may want to login, but using wrong credentials.
* This route should be free for all unauthenticated users.
* Users should be warned when login fails
*/
public function testLoginWithWrongData()
{
// as a user, I wrongly type my email and password
$data = ['email' => 'email', 'password' => 'password'];
// and I submit it to the login api
$response = $this->call('POST', 'login', $data);
// I shouldnt be able to login with wrong data
$this->assertEquals(HttpResponse::HTTP_UNAUTHORIZED, $response->status());
}
/**
* User may want to login.
* This route should be free for all unauthenticated users.
* User should receive an JWT token
*/
public function testLoginSuccesfull()
{
// as a user, I wrongly type my email and password
$data = ['email' => 'admin@app.com', 'password' => 'secret'];
// and I submit it to the login api
$response = $this->call('POST', 'login', $data);
// I should be able to login
$this->assertEquals(HttpResponse::HTTP_ACCEPTED, $response->status());
// assert there is a TOKEN on the response
$content = json_decode($response->getContent());
$this->assertObjectHasAttribute('token', $content);
$this->assertNotEmpty($content->token);
}
}
All tests are passing, the only exception is testGetMainAuthenticated. Its failling, and I dont know why. Apparently, while testing, JWT is unable to get the Authorization header.
Any thoughts?
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 2
- Comments: 26 (3 by maintainers)
@jadjoubran Found the solution. You need to add $this->refreshApplication(); under clientGet before requesting. As referenced here:
https://laracasts.com/discuss/channels/testing/laravel-testig-request-setting-header https://laracasts.com/discuss/channels/testing/laravel-testcase-not-sending-authorization-headers
Instead of parsing the token in the
headers, set it as aqueryparam like..?token={token}then have a method in a class like:Your controller can get the
tokenby invoking:$request->input('token');and pass it on the class you created with theisValidToken()method.Hope someone finds this useful!
I don’t know if it’s really right solution, but You can make a per-test authentication directly from your test cases. For simplicity, I have created a method in my test case (it can be trait anyway)
Then I just call
$this->authenticate(User::first());before making a request.I hope this will be helpful.
I’ve experienced the same issue.
Route works when manually tested, headers are sent in testing, but JWT-Auth mysteriously never read them. Did a bunch of debugging, determined that for some reason the request wasn’t being properly set insite JWT-Auth.
Workaround was to add this to my controller:
I’m currently facing this issue with both
Codeceptionand Laravel Tests powered by PHPUnit. Both methods (?token and HTTP Header) does not work. Codeception$I->amBearerAuthenticatedalso gives “Could not parse token from the request”.The only way I was able to put a band-aid on it was creating a middleware and setting it to all my routes in the
Kernel Middlewareattribute.I tried installing version 1.0.0-alpha-2 to see if it would work, but the installation gives me the following error:
@tymondesigns I’ll test when I get some time.
For now, this is the best bandaid solution I can come up with
That will set the correct request object regardless of where it came from.
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. This is a known issue, but not one likely to change. Automatically refreshing components in tests would be a drastic change, and it would be very difficult to distinguish when it’s necessary or useful.Yes, you can force the whole application to refresh with
$this->refreshApplication(), or there may be a few ways to force JWTAuth specifically to refresh what is necessary. (perhaps something like @rlugge’s use ofJWTAuth::setRequest())However, if you have the time, I would personally advocate for breaking your tests up into smaller pieces. You can have each request in its own test. If your test requires an existing state, like an invalidated token, you can create that manually. That way you aren’t accidentally testing too many interactions and having a hard time separating them. (Indeed, to many people, even a single server request is already too complex for a “unit test”, though the Laravel framework is of the opinion that it’s fine.)
If you really want these large-scale multi-request tests, your tests are definitely more “functional” than “unit”, and you should probably look into a functional testing framework like Codeception or Behat.
Have exactly same issue. Infact keep getting following:
2015-10-27 16:02:49] testing.ERROR: exception ‘Tymon\JWTAuth\Exceptions\JWTException’ with message ‘The token could not be parsed from the request’ in /vagrant/clip-team/vendor/tymon/jwt-auth/src/JWTAuth.php:195
I used this (5.4) in my TestCase class based on what @SomethingWrong posted:
The only problem with this is that if you are using sqlite :memory: it also wipes the database.
I ended up with the following:
http://hocza.com/2017-01-09/day-1-lumen-jwt-testing-tymon-jwtauth/
How does that work when everything is reset once the request is made?
We have the following function on
TestCase.phpto generate auth headers.@tymondesigns Sorry to post here in this closed issue but I’m having the same problem but in my case in Lumen 5.2. If you prefer, I can open a new issue.
I have the 0.6.*@dev version of jwt-auth and everything works fine outside the test environment but when I try to run the tests with phpunit I get a exception ‘Tymon\JWTAuth\Exceptions\JWTException’ with message ‘A token is required’ in JWT:237.
My routes are defined to go through the jwt.auth middleware provided. In the setup of my tests I create a token and send it with each request. I checked that the request has the correct header so I think the problem is with JWT-Auth not reading it correctly as @tjdavenport suggested.
Can anyone give me any advice on how I can solve this or a workaround for this issue?
Thank you!
Edit: The real problem is that when you run the tests with PHPUnit you can’t access the token through the JWTAuth facade. I manage to solve this by not using the facades to retrieve the tokens, which is probably a better solution.
Not sure if this is relevant in your case, but I was having the same sort of issue with a Dingo API.
In my phpunit.xml file I had to specify localhost for the API domain and then my auth headers would be picked up.
I fear that your other tests where giving false positives by checking for the response status. To be safe, I would change your asserts to
$this->seeJsonContains()and check a message so that you can be sure you are failing authentication rather than checking a generic 401 response.