Learn-json-web-tokens: Testing API that uses JWTs

Created on 8 Jul 2015  ·  5Comments  ·  Source: dwyl/learn-json-web-tokens

I hope this is the appropriate place to ask questions so I can learn! I'm pretty brand new to testing, and I'm very interested in all the benefits a test suite provides. That said it can be really hard to know where to start.

I have an API that uses JWT for authentication. I'm interested in a high-level explanation as to how to setting up testing in such an environment. I assume that when unit testing my controllers I don't want to simultaneously be testing authentication. I'm sort of confused by how to mock out authentication so I can test my endpoints without getting 401s.

Most helpful comment

@rhewitt22 we prefer the approach described by @walling
(where tests more closely resemble "integration" instead of "unit" tests)
for _precisely_ that reason. Everything you mock is "_fake_" and thus you won't know when something "_real_" works or not (_so bugs are more likely_).

We've inherited (_large_) projects where people _forget_ to update the mocks when they change methods and as a result the tests do not reflect the _reality_; _nightmare to debug_!

Always ask yourself: "_(Why) do we need to mock this?_"

If you are mocking something because its out of your control e.g. a 3rd party service or network device,
it makes sense. But if you are mocking your _own stack_ you've got consider that you might be over-complicating things...

The clue in your _question_ is in the term "_API_", this indicates that you aren't testing a "unit", but rather an (API) "_endpoint_".

Hit the endpoint and if you get a 401 you know you need to _authenticate_! (_Good News! Your app works as expected!_)

Here's a _working example_ of a test that does _exactly what you need_:
https://github.com/dwyl/hapi-auth-jwt2/blob/f17bac65d40b2a9154da84390b874cae4e1de192/test/test.js#L119-L135

Then I'd Recommend Reading:

tl;dr

If your _own_ code is too complex and you feel the _need_ to mock it, _first simplify your code_!!

Also, we wrote our hapi-auth-jwt2 Plugin so others can rely on our testing. And released a _linear scalable_ Redis-backed example: https://github.com/dwyl/hapi-auth-jwt2-example so people can copy-paste (_tested_) code! :wink:

_We_ only mock when our tests "_feel too slow_" otherwise we _always_ exercise as much of the stack as _possible_ on each test pass (while limiting dependencies to only the _essential_).

If an element of your App's functionality requires authentication, why don't you use that as an _opportunity_ to _exercise_ your auth/verify methods? Ultimately, knowing how _performant_ your auth is will be _good_ for your app because auth/verify is one of the biggest bottlenecks in your stack! (i.e. _every_ GET/POST/PUT/DELETE _request_ has to go through auth/verify so it should not take more than a couple of milliseconds ... anymore than that and your app _won't scale_!)

Remember: Don't _assume anything_ and make up your own mind on how it makes sense to test your application. If there was a "right way" of doing testing, all the universities would be teaching it ... there isn't. The "best way" is the "_pragmatic_" approach; do what makes sense for _your_ project.

Hope that helps. if not, please let us know where you are stuck! :+1:

All 5 comments

Well, I have some suggestions that might work:

  1. Setup a _test_ client in your system and hardcode a token in the tests. Keep authentication on.
  2. Make sure that authentication is off, when running the tests. I guess there is different ways of doing that. For example in Hapi you can leave out the default authentication scheme, and only set one, when the server actually runs (but not when running tests).
  3. Mock out the stack, so you're only unit testing the controller logic, without making requests through the system. It might not be feasible in some frameworks though.

I'm working on some systems, where we chose (1). The tests then looks a bit more like integration tests and not unit tests, because they run through the stack. On the other hand, there is no disparity between the test and production environment.

You can set NODE_ENV=test, when running the tests (fx. -e parameter in lab), if you want to determine the environment in code. However, you should be aware that the test and production environment doesn't differ too much, otherwise you're not really testing the production environment.

@rhewitt22 we prefer the approach described by @walling
(where tests more closely resemble "integration" instead of "unit" tests)
for _precisely_ that reason. Everything you mock is "_fake_" and thus you won't know when something "_real_" works or not (_so bugs are more likely_).

We've inherited (_large_) projects where people _forget_ to update the mocks when they change methods and as a result the tests do not reflect the _reality_; _nightmare to debug_!

Always ask yourself: "_(Why) do we need to mock this?_"

If you are mocking something because its out of your control e.g. a 3rd party service or network device,
it makes sense. But if you are mocking your _own stack_ you've got consider that you might be over-complicating things...

The clue in your _question_ is in the term "_API_", this indicates that you aren't testing a "unit", but rather an (API) "_endpoint_".

Hit the endpoint and if you get a 401 you know you need to _authenticate_! (_Good News! Your app works as expected!_)

Here's a _working example_ of a test that does _exactly what you need_:
https://github.com/dwyl/hapi-auth-jwt2/blob/f17bac65d40b2a9154da84390b874cae4e1de192/test/test.js#L119-L135

Then I'd Recommend Reading:

tl;dr

If your _own_ code is too complex and you feel the _need_ to mock it, _first simplify your code_!!

Also, we wrote our hapi-auth-jwt2 Plugin so others can rely on our testing. And released a _linear scalable_ Redis-backed example: https://github.com/dwyl/hapi-auth-jwt2-example so people can copy-paste (_tested_) code! :wink:

_We_ only mock when our tests "_feel too slow_" otherwise we _always_ exercise as much of the stack as _possible_ on each test pass (while limiting dependencies to only the _essential_).

If an element of your App's functionality requires authentication, why don't you use that as an _opportunity_ to _exercise_ your auth/verify methods? Ultimately, knowing how _performant_ your auth is will be _good_ for your app because auth/verify is one of the biggest bottlenecks in your stack! (i.e. _every_ GET/POST/PUT/DELETE _request_ has to go through auth/verify so it should not take more than a couple of milliseconds ... anymore than that and your app _won't scale_!)

Remember: Don't _assume anything_ and make up your own mind on how it makes sense to test your application. If there was a "right way" of doing testing, all the universities would be teaching it ... there isn't. The "best way" is the "_pragmatic_" approach; do what makes sense for _your_ project.

Hope that helps. if not, please let us know where you are stuck! :+1:

Thank you both! This is incredibly helpful. Based on your suggestion I think this setup lends itself to integration testing -- I definitely want to know if these pieces are working together.

I'm only using oAuth as my authentication method (no username/password). I'm interested in learning more about how to create a test client. I know that I don't want to go through the entire oAuth flow, but as you suggested hardcode a token that I can use in my tests. Could you recommend any resources that might help me wrap my head around creating a test client?

If it helps my current project uses SailsJS v11.0. I have a bootstrap test file that starts my server and creates/populates an in-memory database with fixtures. Might that be an appropriate place to create a test client?

Thank you again -- it's wonderful to find people so willing to share their knowledge.

@rhewitt22, well, in my case the test client was as simple as creating a non-expiring JWT token signed with the correct payload and secret key. Maybe that helps.

In OAuth, I think it depends on the authentication flow. Maybe you can even create the final access token with certain permissions and that doesn't expire, so you don't have to go through the flow each time.

Beware of the security concerns about hardcoding a non-expiring token in your tests, if that token also grants a lot of permissions in the production system. Then a potential attacker would only need this token to get access. You might want some way to grant access to certain clients, and when running the tests you can grant access to your test client or token. I hope it all makes sense.

@walling

Thanks for the advice!

I just went ahead and created a fake user, skipping the oAuth flow, in my tests and granted them the same JWT (with expiration) that I use in production. All my tests are passing, and I'm a very happy camper.

:smile: :smile: :smile:

I figure that as I'm working on my app and looking at the development server I'll be ensuring the oAuth portion works as expected.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

joepie91 picture joepie91  ·  18Comments

rjmk picture rjmk  ·  9Comments

sarneeh picture sarneeh  ·  3Comments

nelsonic picture nelsonic  ·  5Comments

NE-SmallTown picture NE-SmallTown  ·  5Comments