All about JWT (part-2 Node-Server)

Javed Akhtar
14 min readFeb 18, 2023

--

Image explaining working of JWT with access and refresh token using 3 endpoints.

Hey! Geeks welcome to the 2nd-part of my third article on this platform. If you have not gone through 1st-part of this article then please go through that first from here, I highly recommend it⚠️. In the 1st-part of this article I have explained the basics of JWT — “how we are going to implement JWT using Node and React”. 🔥Here in 2nd-part of this article we will understand only back-end(Node server) and then we will jump to front-end in the 3rd-part of this article🔥. If you still have doubts, please check my YouTube videos 📺 where I have explained this article in detail with code.

Back-end Implementation using Node

The aim of the code structure is to explain JWT while keeping the code standard to near to prod which you can further break more to make it more modular. You can check the entire code of Node server we are going to create from my Github repository JWT-Register-Login-Node-Server .
Clone it while reading this article for better understanding and play with it.

Prerequisite❤️‍🔥:

  • Basics understanding of node and use of express framework.
  • Clone the node repository JWT-Register-Login-Node-Server and open it 📖 while reading as I have not added all code to this article in order to keep the article short.

Time saving tips⏰ : If you are an experience developer👴🏽 then you might not be interested in basic understanding of code structure. So, you can simply jump to “Understanding auth controller, heart of JWT implementation” section of this article to understand how JWT is implemented with cookies🍪.
But if you have time I will still recommend to not skip.

Concept summary 🫵

Before diving🤿 into the code, lets try to understand the overall concept which we are going to code.

The concept for creating the back-end is very simple. We are going to create mainly 4 APIs /register, /login, top/:number, /rtoken which are self explanatory of what they stand for.

/rtoken is the API which will be used to create a fresh access token after verification of the refresh token 🔑(received from the requestor) received as cookie🍪. You can ignore the remaining apis in the code, they are redundant for our understanding.

/login is the api in which both access and refresh JWT tokens 🔑 created and we deal with cookies🍪 to set in the browser of the front-end app.

NPM packages📦 used in our back-end code:

  1. express: express framework using which we will give structure to APIs
  2. morgan: logger middleware for node, helps in debugging.
  3. body-parser: responsible for parsing the incoming request bodies in a middleware before you handle it
  4. cookie-parser: parses cookies and make it available to request.
  5. jsonwebtoken: to generate jwt token
  6. bcryptjs: to hash the password sent for new registration.
  7. dotenv: so that we can use some environment variable in .env file.
  8. pg: since we are going to use postgress as DB.

Our back-end code folder📁 structure will look this:

back-end code folder structure

Lets try to understand each one of them:

1. Root Folder :

a) index.js: this is our entry point where we provide ports and start server.
b) app.js: here we will parse the request using middlewares, routing etc.
c) .env: to define environment variable.
d) .env.example: the same variable defined in “.env” file but without secret in it. Since we do not push “.env” file, so “.env.example” can help others to create .env file and look for values of the variables.
e)gitignore: to ignore some files(like node modules) while pushing the code.
f) package.json: the package dependency list.
g) package-lock.json: for locking the dependency version installed

2. config :

This folder will contain all the config files, currently it has only one file (config.js) in which we have DB(database) config defined.

3. api :

this folder will have all back-end source code.

Understanding the source code in detail

Lets understand above folder and files in detail and in parallel to this we will be learning how our node server will be built and will end up with complete server (it’s magic, it’s magic 🪄🪄🪄).

index.js :

This file is very basic and easy to understand (already explained above, google it if needed or comment your query if you need any help)

app.js:

This file is also very basic and easy to understand for those who have written code using node server. I will summarize the file just to be more clear. I am skipping to share the whole content of the file here as it will consume more space, clone the repository and open it to understand the summary.
Summary of app.js file: We are first importing the required packages and then using the middlewares to log, parse the request. We are also configuring the CORS by our self as we are not using any CORS library (you can prefer using CORS library). To understand more on CORS please check my CORS article All about CORS.
I just want to highlight two lines of code from the app.js file which has self explanatory comments.

  res.header("Access-Control-Allow-Origin", "http://localhost:3002"); // wildcard(*) not allowed while using withCredentials in request, withCredentials used to include cookie and auth info in request
res.header("Access-Control-Allow-Credentials", true); // this header with true value required in response while using withCredentials in request so that cookie will not be ingnored in the reponse by browser

Here http://localhost:3003 is the host on which the front end is running🏃‍♂️. So, if you have front-end running on different port(say 3000), please update that here as http://localhost:3000. You can not simply put wildcard(*) here (like this res.header(“Access-Control-Allow-Origin”, “*")) because in request header we will have withCredentials present which is used to allow browser to save cookies🍪 (we will understand the reason while understanding the front-end code).

After this we have given routing instruction that this server is expecting request on route “/hackers” and then we have unexpected error handling.

Now lets understand the actual source code which lies inside the folder api.

Understanding api folder (heart💖 of repository)

api folder has 4 folders in it and one utility.js file which is for defining utility functions as name suggests. The 4 folders are :

middleware🌉:

This folder has authenticate.js file from which a function is exported as module(since only one function we have in the file). This function is used as a middleware(a bridge🌉, read more from here) which has logic written where we are receiving ‘authorization’ header which contains JWT
token which we are verifying by library jsonwebtoken.

jwt.verify(token, process.env.TOKEN_SECRET, (err, token_decoded) => {

If you check the arguments of the function verify, it requires token as well as token secret🤐. To make the verification successful, this secret token should be the same secret token which is used while creating this JWT token.
Note : I am using only one secret token for all users, you can use separate secret key for each user but you have to maintain it in DB and also you will have to have an identifier mechanism to identify the secret to be used for verification. Or better you can use one secret key for all users but keep it changing time to time.

The use of this middleware is explained while explaining routes folder.

routes🌎:

This folder is for handling routes. When an endpoint is hit at /hackers, the request will first look for routes which can help to reach to the portion of code specifically written for the endpoint. If you see below code written in routes/hacker.js file, it is simply directing the api request on /hackers/rtoken to authcontroller function get_fresh_access_token which has the logic written to serve this endpoint.

router.get("/rtoken", AuthController.get_fresh_access_token);
router.get("/all", authenticate, HackerController.get_all_hackers);
router.post("/login", AuthController.loginUser);

Now if you see we have authenticate ( a middleware ) as well in the route at the endpoint hackers/all. This is the middleware which works as firewall🏛️ or you can assume it as checkpoint🛂 which will check whether the request is allowed✅ or not ❌ to go to the function get_all_hackers defined in the controller HackerController. If the middleware calls the next middleware i.e. next() then only the control will go to the controller HackerController.get_all_hackers which has code written to serve the request.

This means you can use this middleware to any route which you want to secure(how cool 😎 is that). So, here apis /rtoken and /login route don’t need any authentication i.e. no need to JWT token to be sent as authorization header. But it requires the JWT token for the api /all.

Controller🎮:

As name suggests it controls the app, it contains controller functions. These functions gets called when an endpoint is hit and routed to a particular controller function. The function will have all the logic written in it which can serve the request, requested on the endpoint. For example in below route, when /login api will hit, it will go to function AuthController.loginUser which has the logic written to serve the request.

We have two controllers defined:

auth.js: this contains all controller functions which will handle authentication related requests. i.e the endpoints /register, /login, /rtoken

hacker.js: this contains all controller functions which will handle all CRUD requests related to hackers info.

hackers.js contains very simple functions used mostly to fetch hacker details. So, we will skip this as code is self explanatory. Our main focus is on auth.js which has controller functions written to handle JWT. We will understand auth controller in very detail in a separate section after understanding models folder.

models:

This folder contains files with respect to number of files in controllers folder (one extra file, dbconnection.js). These files contains functions which will first connect to DB and then execute some query as per request and return the query result, that’s it. So, we don’t need to focus on these code as our main focus is to implement JWT concept which we are going to understand in next section. If you are still interested in understanding models, DB setup and connections, you can check my youtube video where I have explained this article in detail with code running.

Note: If you have cloned this JWT-Register-Login-Node-Server then while trying to run this repository in you system, please follow the readme file present in the repository. In the RAEADME.md file I have explained how to setup Database so that our server can store register cred and user data in it. If you have still doubt how to do that, please follow my youtube video here where I have explained all 3-parts of this article.

Understanding auth controller, heart💖 of JWT implementation.

In this controller we are handling mainly 3 requests /register /login /rtokenfor which we have defined 3 functions with 2 additional function for generating access and refresh token. Let’s try to understand each of them separately.

Generating Access Token🔑 :

function generateAccessToken(user_details) {
return jwt.sign(user_details, process.env.TOKEN_SECRET, { expiresIn: "1m" }); //60 //'1h' // '1s', '1m'
}

Here we are using jsonwebtoken npm package and imported it as jwt to generate jwt token using it’s sign function. The default algorithm used to sign is HS256. The sign function syntax is
jwt.sign(payload, secretOrPrivateKey, [options, callback]) where

payload : is the detail you want to add to the token which will be added to the first part of the token (as explained in the first part of this article).

Note: If you want this token to be different every time the function gets called, make sure the payload should be a JavaScript object and not stringified object or string.

secretOrPrivateKey : is the secret key we use to sign the token and also used to verify the token. If you check the code in the repository, I have used symmetric key (just one secret key) to sign the token to make this tutorial easy and focused. In prod you may want to use asymmetric key instead in which you will pass a private key (not in scope of this article).

options : there are lot of options available which you can check from here, we are only using expiresIn. expiresIn is the options which we set with time as value, after this time the token gets invalid. For example if we have set it’s value to 1 minute then after generating the token it’s validity will be only 1 minute. So, for only one minute this token will be used by frontend or the client, after this a new token needs to be generated. We have used 1 minute for our POC but in prod it could be from 20 minutes to 60 minutes.

Generating Refresh Token 🔑:

function generateRefreshToken(username) {
const refresh_token = jwt.sign(username, process.env.REFRESH_TOKEN_SECRET, {
expiresIn: "5m",
});
return refresh_token;
}

Generating refresh token is same as generating access token as both are just JWT token but with different param values.
In refresh token we are using different payload, secret, and different expiresIn time. For refresh token ideally expiresIn time is longer than from accessToken becasue we use refresh token to refresh the accesstoken. The expiresIn for refresh token varies depending on use case
(1 day, 90 days, 1 year, etc). For our POC we are using 5 minutes.

Lets try to understand the implementation of the 3 endpoints /register /login /rtoken.

Handling registration request at endpoint /register

exports.registerUser = (req, res, next) => {
let response = {
message: "Handling post requests to /resgister",
data: "",
};

data = {
name: req.body.name.trim(),
user_name: req.body.user_name.trim(),
password: bcrypt.hashSync(req.body.password, 8),
user_type: req.body.user_type.trim(),
};
if (isDataContainsEmpty(data)) {
response.data = { desc: "Empty or null values are not allowed" };
res.status(409).json(response);
} else {
console.log("registering user...........");

authModel
.register_hacker(data)
.then((response_modal) => {
if (response_modal == false) {
response.data = {
duplicate: true,
desc: "Username is taken, please try other unique_name",
};
res.status(409).json(response);
} else {
response.data = {
duplicate: false,
desc: "user registered successfully",
};
res.status(200).json(response);
}
})
.catch((e) => {
console.log(e);
});
}
};

/register code summary: In above function I am first collecting data sent in request body and validating them to check if they are not empty. If data is not empty then I am querying DB using authModel which will return false if user is already registered else it will return true meaning user is not duplicate and is registered successfully.

Handling login request at endpoint /login

exports.loginUser = (req, res, next) => {
const user_name = req.body.user_name;
const password = req.body.password;

authModel
.validate_user(user_name, password)
.then((response_modal) => {
let response = {
message: "Handling post requests to /login",
data: "",
};
if (response_modal == false) {
response.data = {
user_exists: response_modal,
desc: "Invalid user, Please try with correct login details",
};
res.status(401).json(response);
} else {
authModel.get_one_hacker(req.body.user_name).then((user_details) => {
const token = generateAccessToken(user_details);
const refresh_token = generateRefreshToken({
username: req.body.user_name,
});
//Here I may need to set expiry of the cookie to 1 year brcause anyway the cookie stores refresh token which can expire
// res.cookie("refresh_token", 'refresh_token_value', { sameSite: 'none', secure: true})
res.cookie("refresh_token", refresh_token, {
secure: true,
maxAge: 3600000,
httpOnly: true,
sameSite: "none",
}); //for 1 hour = 3600000 ms//
response.data = {
user_exists: response_modal,
token: token,
desc: "user is successfully loggedin",
};
res.status(200).json(response);
});
}
})
.catch((e) => console.log(e));
};

/login code summary : Here I am receiving credential from request body and then validating the user by querying DB using authModel.validate_user
If user credential is not matched from DB, respond with 401. If user is valid then I am generating access and refresh token (already explained above).
Now, we are we are sending two response to the requestor:

1. cookie🍪 response:

In this response I have added cookie name “refresh_token” which will contain refresh token value and will be stored in browser to be accessible by server only and not accessible to clientside script.
This is achieved by httpOnly option while setting the cookie.
Note: browser does sent cookie to every request if the criteria matches.

Following options of cookie are very useful while setting the cookie and also deciding if the request will contain the cookie or not:

secure: setting it to true to make sure it is sent only via https and not http(except on localhost)
httpOnly: to make sure that this cookie is only accessible by server and not by clientside script.
sameSite: None specifies that cookies are sent on both originating and cross-site requests, but only in secure contexts (i.e., if SameSite=None then the Secure attribute must also be set). Please check for other values to make it more secure.
maxAge: the time in ms after which this cookie will not be sent by browser to server with any request from .
path: we are not using it, by default it is / meaning cookies will be sent for all paths of the set domain.
domain: we are not using it, default value is the same domain which has set the request, meaning only the request which will contain the domain will have this cookie added in the request. In our case it is localhost domain sending the request a default domain.

For more on cookie🍪, you can check here.

2. dataℹ️ response:

In this response I have added access token as well as confirmation of user existence.

Handling refresh token request at endpoint /rtoken

exports.get_fresh_access_token = async (req, res, next) => {
let response = {
message: "Handling get requests to /rtoken",
data: "",
};
//check if refresh tocken is sent by browser in cookies, if not the cookie may have expired, so throw 401 and client should logout.
// to check cookies getting received, use curl: curl http://127.0.0.1:4000/hackers/rtoken --cookie "refresh-token=test"

refresh_token_from_client = req.cookies["refresh_token"];

try {
if (refresh_token_from_client == null) throw "token not found";

jwt.verify(
refresh_token_from_client,
process.env.REFRESH_TOKEN_SECRET,
(err, token_decoded) => {
if (err) throw err;

authModel
.get_one_hacker(token_decoded.username)
.then((user_details) => {
const freshAccessToken = generateAccessToken(user_details);
response.data = {
token: freshAccessToken,
desc: "a fresh access token is generated",
};

res.status(200).json(response);
});
}
);
} catch (error) {
const response = {
message: "Auth failed, refreshToken Expired",
error: error,
failureCode: "RefreshTokenExpiredError",
};
console.log("refresh token error.....", error);

return res.status(401).json(response);
}
};

/rtoken code summary: Here I am receiving a cookie named refresh_token which we had set it in browser when user did login. This token was sent in request by browser because browser does sent cookie to every request if the criteria matches i.e. if values of Path, HttpOnly, Secure, SameSite, Domain, maxAge satisfies (as explained in /login request explanation).
Now, after this I am checking if the cookie received is not null and if not null then I am verifying the token with the same secret key with which refresh token was created.

Once the token is verified, I am generating a fresh access token for the user and returning it to the requester.

In any case if it is failing to verify the token I am throwing error which I is catched and respond with 401, meaning user needs to be logged out and relogin is required.

Important Links

Final Thoughts:

  • I hope this article helped you to understand JWT concept in server side with node and encouraging you to implement it by yourself.
  • I would love to hear your feedback on this article, you can encourage me by clapping👏 and share feedback by commenting what you liked 👍 and what you didn’t 🥲. Please let me know by commenting 💬 if I have explained something which needs to be corrected because at the end, this is how we improve information on internet.
  • It’s 💯 OK if you don’t want to share this article to anyone, but please share your learning to people/developers/juniors/seniors who don’t know yet about JWT implementation, this will serve the sole purpose of this article. Thanks a lot for spending time in reading this article specially in this era of short videos where we get bored so easily.

--

--

Javed Akhtar

I am a software engineer. I love javascript and it's library(React). I am also a writer, cook, singer, youtuber. website: https://javedinfinite.com