Add a JWT Verification Middleware

Note About Breaking Changes

At the time of recording, express-jwt was at version 5. It is now at version 6 which includes some breaking changes.

In particular, the algorithms key is now mandatory.

Please use an algorithm of HS256 as follows:

const checkJwt = jwt({
  secret: process.env.JWT_SECRET,
  issuer: 'api.orbit',
  audience: 'api.orbit',
  algorithms: ['HS256']
});

Ryan Chenkie:
[0:00] If there was just one thing that I can encourage you to do as you are building out security for your app, it would be to ensure that your API endpoints are properly locked down.

[0:09] We can spend as much time as we want, building out a security features for the front end application for the React App. Ultimately, an application that runs in a browser like a single page app, is just inherently insecure in of itself. That's because browsers are public clients.

[0:26] Anyone who loads an application can just come in and inspect the code that goes to the application that can fiddle with local storage. As soon as the application code hits the user's browser, it's out of our control. They can do whatever they want with it.

[0:39] They key is going to be that you properly lock down your API endpoints. Right now, this dashboard data endpoints -- if we make a request to it -- it's sending our JSON Web Token as an authorization header, but there's no security feature on the backend right now to actually enforce that that JSON Web Token is present and that it's valid.

[0:58] What we want to do is guard our endpoints such that JSON Web Token must be in the request, and it must be valid before the endpoint can send back data. Let's work on that.

[1:08] Let's go over here into orbit API, and we'll go to server.js, this is the endpoint that serves our dashboard data. All that it's doing right now is just responding with some JSON data which is found in this data folder in dashboard.js.

[1:24] Our goal now is to prevent this API from sending back data, unless there's a valid JSON Web Token that comes in the request. To do that, we are going to use a middleware.

[1:34] If you are unfamiliar with middlewares, they are essentially this layer that request need to pass through under way to endpoints. Because requests need to pass through them, we've got an opportunity to act on those requests as they do pass through.

[1:49] We can do some check for something in the request itself. We can attach metadata to the request before it hits the endpoint. We've got lots of options here, and this is good use case for enforcing authorization.

[2:00] If we were to construct our own middleware, it would look something like this in Express. We would say app.use, and then we would give a call back function that's got a request, a response, and a next function.

[2:12] Now, the request is going to have information about the incoming request. This is where we'll see things like our authorization header on the header's object. The response is going to give us some utilities to respond to the client in different ways, so we can respond with JSON data, we can set different status codes for our responses, that sort of thing.

[2:30] Then, Next is going to give us a function to pass control onto the Next function, which would in this case would be our dashboard data endpoint. Just to get a sense of how this works, we can come over here and we'll log out the request header. Say, console.log_RequestHeaders.

[2:47] Then, we need to call Next. Otherwise, this will just be a pending request that would sit there forever -- or at least until the browser times out. Let's save this and now if we refresh over here on our dashboard. What we see is all of the headers that come in the request, including our authorization header.

[3:06] We could construct a middleware ourselves where we take that header from the request. To the authorization header, we could verify the JSON Web Token that's in it, and then either error auth if it's invalid or pass control onto the endpoint.

[3:19] Instead, why don't we use library that does for us already? Come over into a new terminal window, and CDN2Orbit-api. Do NPM install express-jwt. Once we got that, we will require it at the top here. May be [inaudible] we can say constantJwt -- I like to call it JWT usually -- equals Require_Express-jwt. Then down here, let's make use of it.

[3:50] Back over at our dashboard data function, let's get rid of this middleware that just created, and now let's find a middleware that we can apply to the various endpoints that we've got. Let's give ourselves a constant of checkJwt, so just to check that the jwt is there. That's going to be equal to jwt passing in an object that has certain properties to find.

[4:11] We'll want a secret, and that's secret has to point to process.env.JWT_SECRET. Just as a reminder that value is found here in the .env file, so JWT_SECRET here. The secret is used to sign a token, and we need it now to verify the token as well.

[4:29] The other thing that we'll want to check here is that we have the correct issuer and audience on the token. Depending on your setup, you might have various JSON web tokens that are intended for various different audiences. Meaning that you might have an authorization server that signs tokens that are intended for different APIs.

[4:47] Now in our case, we only have a single API and it happens to be the API that signs the token as well. In our case, the issuer is API.orbit -- that's what we have set up in our token -- and the audience is API.orbit as well.

[5:02] These are really more OAF concerns. This is kind of thing you would want to enforce when you're implementing OAF, but it's good idea to get into the practice of doing this in here as well.

[5:12] Now that we got that middleware, we can actually stick it into any of these endpoints that we want. There's couple ways to actually apply it to our endpoints. Probably the simplest way is just stick into right in here as the second argument to our endpoint.

[5:26] If we knew that we want to apply this middleware to everything that is found beneath here, what we could do is a app.use and then apply checkJwt like that. In some cases, you might want to be more specific with where that check actually goes.

[5:40] Let's see if this is going to work for us. We'll save this and make sure that our server has restarted. Now, let's try refreshing. When we refresh, we get the data back that's what we would expect because we're still sending that authorization header with the JSON web token.

[5:56] Now perhaps we can do is try to validate this token on purpose. One way to do that is to just simply come over local storage -- that's where currently keeping our token -- and just mess with some of the characters in here.

[6:09] We can even just take out a couple of characters that going to mess up the structure of the token and should invalidate this signature. Now if we refresh, what we get is an error here in our API, and the error comes in the verification step. It says, "Unauthorized error. Invalid token." Because we got an invalid token, we got a 401 response.

[6:32] Here's our request with a status code of 401 Unauthorized. That 401 response is produced in this jwt middleware, in the middleware that is employed by express-jwt.

[6:45] This is one way to set up the middleware pretty easily. If we wanted to get a little bit more fine grains, have little bit more control, we can of course set our own middleware up. This express-jwt middleware is an easy to use tool that does pretty much everything we would need out of the box.