DEV Community

Marc Backes
Marc Backes

Posted on • Originally published at codewall.co.uk

Secure Your Node.js Application With JSON Web Token

Cross-posted from CodeWall
When you build a web application where your front-end and back-end is separated, one way of putting it behind a login is with JSONWebToken. It is a concept that became popular very quickly when it was introduced in the early 2010's. In this post, you'll learn what JSON Web Token (JWT) is, how it works and how to integrate it in your Node.js application. Let's get started!

JSON Web Token in a nutshell

Quoting the official website, "JSON Web Token is an open, industry standard method for representing claims securely between two parties". Which means, a server can determine whether an information (in JSON format) sent by the client has not been modified and has effectively been issued by said server.

What does a token include?

A JSON Web Token is composed of three parts:

  • 📃 Header: Contains extra information what kind of token it is (JWT) and which signing algorithm is being used (e.g. SHA256). The header JSON is being Base64Url encoded.
  • 📦 Payload: Contains information (or "claims") that the two parties want to share. This could include anything you want, but it's never a good idea to share sensitive data (such as passwords), because by default, a JWT token can be decoded without a shared secret. JWT does not have the goal to encrypt the data. I personally usually use user ID, role, issue date and expiration date. As well as the header JSON, the payload JSON is also encoded with Base64Url.
  • 🔏 Signature: The signature contains the encoded header, encoded payload, a secret (that only your server knows) and is signed by the algorithm that's determined in the header.

Were the hashing algorithm to be SHA256, the signature would be created as such:

HMACSHA256(
base64UrlEncode(header)
+ "."
+ base64UrlEncode(payload)
,secret)
Enter fullscreen mode Exit fullscreen mode

At the end, all three parts are just being concatenated, separated by a ".":

<Header>.<Payload>.<Signature>
Enter fullscreen mode Exit fullscreen mode

Here an example JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZDM5YzI4MjdhZDgyMjBmMTJiMGZkMWIiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE1NjUyNzE4NzUsImV4cCI6MTU2NjQ4MTQ3NX0.NmcvfTBmUw1cdEI1cNRHzq6q5W0HmPELoh8rd_7EFAc
Enter fullscreen mode Exit fullscreen mode

Take a moment to head over to jwt.io and paste the token in there. You'll see all the information that it contains (except the signature)

How does a JWT token work?

It is important to notice that JWT is not meant to exchange encrypted data. It should never contain sensible information such as passwords. The key in JWT lays in the verification of tokens. When you try to verify a token which has been tampered with (maybe a user ID has been swapped), the token will be rejected.

Why? Because the content does not match up with the signature anymore. So a valid token cannot be created by somebody other than you, except when they get the hands on your secret you use to hash the signature with.

In case your JWT secret gets hacked for some reason, you need to change it immediately. All already existing tokens from then on will be invalid. Which might be a little annoying for some logged-in users, but you can make sure that nobody can generate a valid token for your application.

How does a JWT workflow look like on a server?

JWT Workflow Between Client And Server

Now that we are a little familiar with JWT in general, let's take a look at an example how it would work with a client-server exchange.

  1. The first move makes the client. This could be a web frontend application, a mobile app, etc. Basically anything that tries to interact with your backend application (for example a REST API). It sends their login credentials to the server for it to become verified.

  2. When the server receives the login request, it first makes sure that the username/email and password match up with information stored in the database. When the credentials are correct, this means for the server that this user is who he says he is.

  3. Next, the JWT token is being generated. Here, information that's important for identifying the user are being passed into the payload. It's also a good idea to include issue and expiration dates. So a session would never be longer valid than the time you indicate. One week seems like a good time span. The user should log out of the application after each use anyways, right? But this just adds one extra piece of security by avoiding zombie logged-in users.

  4. The token then is returned to the client as a response to his login attempt. When he receives a token, that means for him the login has been successful. The token should be stored somewhere locally on the client-side. This can be localStore for web applications or somewhere in a device variable for mobile applications.

  5. For all further communication with the server, the client adds an Authentication header to each request. This looks as such:
    Authentication: Bearer

  6. When a new request to a protected resource arrives at the server, the first thing it does is to check if an Authentication header is passed along with the request. Is this the case, it tries to verify if the token checks out. If it's not a valid token (it has been tampered with, it has expired, etc.), the request should be denied immediately.

  7. If the token is valid however, it's safe to assume for the server that the user is still who he says he is and can return the requested resource as response to the client.

JWT in a Node.js application

In this post, I'm not going into details how web servers work in Node.js. But I'll show you how you can use JWT in a JavaScript server environment.

Preparation

In order to work with JWT, you can use the handy jsonwebtoken library. Install it as such:

npm install jsonwebtoken
Enter fullscreen mode Exit fullscreen mode

Create a token

At the place in your code where you determine if the client has provided correct login credentials (probably just after you checked the database), you can create the JSON Web Token:

const token = jwt.sign(<Your payload>, <Your JWT secret>, { expiresIn: <Expiration Time> })
Enter fullscreen mode Exit fullscreen mode

In a real example, it could look like this:

const jwt = require('jsonwebtoken')
const token = jwt.sign({ _id: user._id, admin: true }, process.env.JWT_SECRET, { expiresIn: '1 week' })
Enter fullscreen mode Exit fullscreen mode

Notice two things:

  • If you are not familiar with dotenv, process.env.JWT_SECRET is where your JWT secret would be placed. It is never a good idea to store your token as clear text in your code, that's why it's a good idea to use tools such as (dotenv)[https://www.npmjs.com/package/dotenv] to locate it in a file that's not going to be uploaded to your Git repository.
  • The expiredIn property can be human readable time indications in string form: -* '4 days' -* '7 hours' -* '2 weeks' -* '6 months' -* etc.

Verify token

Your client should set the Authentication header as such: Bearer: . Therefore you first need to strip the "Bearer: " part away from the string:

const token = req.header('Authorization').replace('Bearer ', '')
Enter fullscreen mode Exit fullscreen mode

(req.header('Authorization') is the Express.js way to read the authorization header)

Then, you can verify the provided token as such:

const jwt = require('jsonwebtoken')
try{
    const payload = jwt.verify(token, process.env.JWT_SECRET) 
    console.log(payload._id)
} catch(error) {
    console.error(error.message)
}
Enter fullscreen mode Exit fullscreen mode

If the token is valid, you'll have access to all the payload data right in the payload variable. If the token is invalid, the JWT library will throw an error you can treat in catch.

Summary

That's it! As you see it's not that complicated to use JWT. The most important thing -and I can't stress this enough- is that JWT is NOT encrypting your data, therefore DON'T use it to exchange sensible information.
JSON Web Token is an excellent technology to verify whether the information someone claims they have are actually authentic.
Have fun coding!

Top comments (11)

Collapse
 
ssimontis profile image
Scott Simontis

Thank you good sir! I love the way you explored this concept. Authentication has become a black box to most developers these days. Don't get me wrong, services like Azure AD and Auth0 are awesome and great for quick prototyping, but I feel many developers are so used to having authentication abstracted away that they don't really understand what is going on and all. Thank you for reminding everyone that the token is not encrypted and needs to be checked every time.

The only frustrating thing with JWT is revocation, mainly because it's impossible. If you are building a security-critical app where user rights may need to be revoked at any time, JWT may not be the best choice. The workaround is to maintain a revocation list in the database, preferably with the most recent revocations in a cache, and to check the token against the revocation list.

Collapse
 
mikedshaffer profile image
Mike Shaffer

We had the exact scenario and used JWT very successfully. You state it exactly, create a server based revocation list. In a database, memory cache...whatever. On any incoming requests you will have verify() method to you know, verify the JWT. As one of the many steps of verify(), ensure that the user is not in the revocation list. If they are, the request is denied and a response of unauthorized is generated. We also had a requirement that each login could be revoked. In that case we added a unique serial number (actually a timestamp) to the body of each JWT. Added a check to the verify() method to look up the serial number. We then managed both of these list with time to live and fast caching to ensure performance. And this was all for a Fortune 50 Financial Services company with millions of users world wide.

Collapse
 
rishpoddar profile image
Rishabh Poddar

This sounds awesome! I have a question though:
What does the unique serial number achieve that blacklisting the JWT doesn't?

By the way, if you are interested in adding more levels of security while maintaining scalability, have a look at supertokens.io. It's one of the most extensive and well thought out solutions that prevents against all session attacks including detecting session hijacking using rotating refresh tokens. Also, this solution is end to end, taking care of all race conditions and network failure issues, so that developers have a very easy time implementing it. For details on how this works, please visit: supertokens.io/blog/the-best-way-t...

Thanks!

Collapse
 
themarcba profile image
Marc Backes

Wow, this is an amazing story. I see you added some extra steps, which is really cool. Gives me the idea to add a revocation status to my users as well. 👍

Collapse
 
themarcba profile image
Marc Backes

First, thanks for your kind words. I appreciate the encouragement a lot 🤗

I always add generated tokens to the user object, so they could themselves remove other logged-in sessions (upon login I check for users with the given id AND token in user object). To block someone, you can delete all their existing tokens and set a flag in the user object to deny any further token generation.

I could write up another blog post on this when I get some time

Collapse
 
brokenthorn profile image
Paul-Sebastian Manole

Read some blogs but basically you need to issue JWT renewals based on previous payload or token and that way maintain a session if needed or at least maintain a chain of tokens but renewal requests need to be issued by the client before current token expires otherwise you can't have secure renewals.

Collapse
 
themarcba profile image
Marc Backes

I don't know about the best practices about this case, but here is my opinion about it:

You can store the critical workflow data somewhere in the database, attached to the user. Then when he logs in (again), you can pull the information from there.

However, this should not happen very often. If users find themselves getting logged out much by token expiration, I'd recommend just adjusting the expiration time accordingly.

Alternatively, you can show a message, warning the user that in x minutes the session will be closed.

Collapse
 
jitheshkt profile image
Jithesh. KT

Great write up! Sad that it doesn't have any information about refresh token or building a logic to refresh the token securely when it expires that too without kicking the user out.

Collapse
 
pavelloz profile image
Paweł Kowalski

Thank you very much for excellent explanation.

Collapse
 
themarcba profile image
Marc Backes

I am glad I could be of service 😊 Enjoy coding!

Collapse
 
vikaschauhan1 profile image
Vikas Chauhan

I want do develop it in a way that no body can decide it as happens in this case.
Could you please tell me the best solution for this?