JWT Authentication in Express.js

A week ago I’ve spoken about basics of JWT; what are they, why they’re great alternative for cookies, and also, I briefly described different ways of authentication with their usage, as well as ways of matching identity, based on provided token.

Today I’d like to show you, one method on real life example. As Node developer, I’ll naturally demonstrate that on the Express project, although (I believe) similar pattern might be applied on majority of modern backend environments.

Introduction

+-- package.json
+-- app.js
+-- middlewares.js
+-- libs
| +-- auth.js
+-- models
| +-- User.js
| +-- index.js (exports all the files)
+-- routes
| +-- auth.js
| +-- dashboard.js
| +-- index.js (exports all the files)

So as you see, that’s probably just typical express project. Nothing fancy.

Understanding the structure

  • models is where we store all the DB models, mongoose in my case. Not much related JWT happens there.
  • routes is self explanatory — all the routes are stored there. Auth in this case.

Baby Steps

Here’s the JWT package: NPM

So go ahead and type:

npm install jsonwebtoken --save

Once we got that, we will have to import JWT into our project. So, where do we put it in? In my case, as I follow modular pattern and I split the logic into smaller pieces — all JWT gonna be available in /libs/auth.js, which routes and middlewares will be requiring.

libs/auth.js

Verifying the Token

I like to go with async way, I believe it’s more classy. Also, here’s another controversy — I’m wrapping few lines of code into a Promise, “what’s the point to split it like that” you may ask? First of all, I wouldn’t like to share any kind of logic, on my routes, and also, by splitting it like that — I can always reuse the whole auth system.

Creating new Token

So, what’s happening here? We start with passing a details object, as you can see on line 8–11 — i I’m making sure it actually is an object. Why an object? Well, object because it’s easier to manipulate with data that way, and is easier to pass single object, formatted up to you, instead of having to remember the whole param list. Right?

So, then we make sure the we got set token’s maxAge, and so it’s in valid format. If not — we set it to 3600 secs.

Now. Here we got the controversy I mentioned. I used lodash, in order to filter out functions and any values, representing the password — because we don’t need functions to be passed with our payload, and two — because we neither want to publicly share the password. That’s just in case, if we were not careful enough, not to get it out earlier. This definitely isn’t a musthave, and even more — usage lodash is not essential. But is easier. Much easier.

So we got to the point, where we finally sign the token. JWT aren’t following my philosophy of using objects where you can, so we got to remember the order of those arguments. First one is a payload. Then, we pass the secret — remember, this can not be a random string, as you gotta validate your token with it afterwards — and the options object.

Then we of course return the token — as this is synchronous process, we could easily return it straight from the function, that’s just my personal preference, to assign it to the variable.

I’ve set only two params by myself, although there’s more of those, some might be really helpful. Here’s the cheatsheet:

  • algorithm (default: HS256)
  • expiresIn: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, "2 days", "10h", "7d"
  • notBefore: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, "2 days", "10h", "7d"
  • audience
  • issuer
  • jwtid
  • subject
  • noTimestamp
  • header

Export Default

Implementation!

middleware.js

But hey! Another controversy; where we were suppose to look for the token? In the query? That’d be good idea. But why not to make use of the header, and why not to pass it along with other data? That’s completely up to you, I chose to pass the token inside the body.

But what’s below that. That is really important. As we verify the token, we receive it’s payload. So, referring to my recent post, about techniques of JWT implementation — if you decided to go with white/blacklist solution, that’s where you’ll query your Redis server. Although I did not, I chose to store email and user_id details in public key, so I can find user’s identity if I need to. But, that’s what login route is for, we will come to that soon. So I save the payload to req.user and I go foward.

Yeah, but what if token was invalid, and we got an error? No problem, we simply respond with status 400, or status 403. Both codes are valid, as provided token had to be either fake or expired.

routes/auth.js — login

So, except importing express, defining new router, our user model and param checking middleware, we are also importing the lib we made. Or just it’s function. First few steps are just like any other authorisation system:

  1. we query database for the user
  2. we compare provided password, with the one in the returned document’s
  3. we convert our Mongoose model into JSON format and strip it off, of any confidential data.

Easy, right? So I definitely won’t cover this here. What we’re interested in, is what happens after, which is the response. We send the token back, in the response, together with status 200. Response format is really up to you, I always try to follow the same pattern — I respond with JSON, and if request was successful — I add “success” key.

What’s more exciting, is what’s below. The token key/value. Because we’ve put pressure on modularity — we don’t have to do any overthinking, we simply assign to the key, the result from the function, we earlier created.

I won’t be showing you the signup route, as JWT implementation is pretty much the same — you create a user, and create a token, based on it’s parsed result.

routes/dashboard.js

  1. user tries to access the dashboard
  2. user is verified for his token
    a) token is provided and is valid — user get’s passed in,
    b) token is either not provided or is invalid — middleware cuts in, and responds with status 400.

Summary

Software Engineer. Email me at me@polethedev.com