Skip to content

Instantly share code, notes, and snippets.

@cklanac
Last active November 3, 2018 08:50
Show Gist options
  • Save cklanac/40ed8d249ec04ecd4df272778197d171 to your computer and use it in GitHub Desktop.
Save cklanac/40ed8d249ec04ecd4df272778197d171 to your computer and use it in GitHub Desktop.
Challenge 20 Testing JWTs

Challenge - Testing JWTs and Protected Endpoints

In this challenge you will update integration tests to support to test JWT authorization. You can use the Node JWT Auth as a guide.

Access Protected Endpoint

Chia-Http makes accessing endpoints protected by JWTs is relatively easy. Simply create a valid JWT using the site's JWT_SECRET and add it as a Bearer token to each request. But there's a wrinkle, the resources (eg. notes, folders and tags) belong to an specifec individual so the JWT must be created using the seed data. And the tests must be updated to check for the specific user's resources.

We'll tackle this challenge in 2 steps. First, configure the tests to generate a valid JWT and use it to test an endpoint. Second, update the tests to verify the data based on the user.

Create a JWT in beforeEach

Starting with /test/folders.test.js update the beforeEach as follows.

Loop to map over the seedUser to hash the passwords. At first, the map contains an array of pending promises which you can pass to Promise.all(). When all the promises resolved, then loop over the results and update passwords in seedData with the digests. Now you are ready to insert the users along with the folders. Finally, when all the data has been inserted, capture the users and extract the first user and create a valid JWT.

  // Define a token and user so it is accessible in the tests
  let token; 
  let user;

  beforeEach(function () {
    return Promise.all(seedUsers.map(user => User.hashPassword(user.password)))
      .then(digests => {
        seedUsers.forEach((user, i) => user.password = digests[i]);
        return Promise.all([
          User.insertMany(seedUsers),
          Folder.insertMany(seedFolders),
          Folder.createIndexes()
        ]);
      })
      .then(([users]) => {
        user = users[0];
        token = jwt.sign({ user }, JWT_SECRET, { subject: user.username });
      });
  });

Add JWT to request header

Before attempting to add the JWT to your requests you should update your test suite so only one test is executed. This allows you to focus on one test at a time. In the example below we are focusing on the GET /api/folders. Add .only or .skip to the describe and it blocks as needed.

Now we need to add the JWT to the Authorization header as a Bearer token. Luckily, the Chai-HTTP .set() method makes this easy. Below is a generic example

  chai.request(app)
    .get('/api/protected')
    .set('Authorization', `Bearer ${token}`)
    .then(() => {
      /*...*/
    });

Add the Authorization header with the Bearer token to the /api/folders/ test as shown below.

  describe.only('GET /api/folders', function () {
    it('should return the correct number of folders', function () {
      const dbPromise = Folder.find();
      const apiPromise = chai.request(app)
        .get('/api/folders')
        .set('Authorization', `Bearer ${token}`); // <<== Add this

      return Promise.all([dbPromise, apiPromise])
        .then(([data, res]) => {
          expect(res).to.have.status(200);
          expect(res).to.be.json;
          expect(res.body).to.be.a('array');
          expect(res.body).to.have.length(data.length);
        });
    });

Now when you run the test, you should no longer get an Unauthorized error, but the test will not pass. Comment-out the expect(res.body).to.have.length(data.length) assertion and run it again.

Let's fix the length issue now.

Update tests to expect specific user content

The test did not pass because the /api/folders/ endpoint returns results for a specific user. But the database cross-check is still selecting all folders. You need to update the Folder.find() to include the User id like so:

  Folder.find({ userId: user.id })

The complete test will look like this:

  it('should return the correct number of folders', function () {
    const dbPromise = Folder.find({ userId: user.id });
    const apiPromise = chai.request(app)
      .get('/api/folders')
      .set('Authorization', `Bearer ${token}`);

    return Promise.all([dbPromise, apiPromise])
      .then(([data, res]) => {
        expect(res).to.have.status(200);
        expect(res).to.be.json;
        expect(res.body).to.be.a('array');
        expect(res.body).to.have.length(data.length);
      });
  });

Let's try another one. The next test verifies that the response contains the correct keys. The updated endpoint now returns the userId in addition to the id and name. Update your test with the Authorization header, add a filter to the database query and update the assertion as shown below.

  it('should return a list with the correct right fields', function () {
    const dbPromise = Folder.find({ userId: user.id }); // <<== Add filter on User Id
    const apiPromise = chai.request(app)
      .get('/api/folders')
      .set('Authorization', `Bearer ${token}`); // <<== Add Authorization header

    return Promise.all([dbPromise, apiPromise])
      .then(([data, res]) => {
        expect(res).to.have.status(200);
        expect(res).to.be.json;
        expect(res.body).to.be.a('array');
        res.body.forEach(function (item) {
          expect(item).to.be.a('object');
          expect(item).to.have.keys('id', 'name', 'userId', 'createdAt', 'updatedAt');  // <<== Update assertion
        });
      });
  });

Your challenge!

Update the rest of the Folder tests and then apply your learning to the Tag and Note tests.

Deploy the app

If time allows, you should push your changes to GitHub and verify your tests pass on Travis CI and are deployed to Heroku.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment