Learn how to build a full-stack MERN App using JWT authentication

Originally published by Praveen Kumar at https://blog.logrocket.com

What is JWT?

JSON Web Tokens are an open, industry-standard RFC 7519 method for representing claims securely between two parties. A JWT is a JSON response that is defined in RFC 7519 as a safe way to represent a set of information between two parties. The token is composed of a header, a payload, and a signature.

In simple words, a JWT is just a string in the following format:

JWT string format.

It should be noted that a doublequoted string is actually considered a valid JSON response:

"header.payload.signature"

JWT flow

Since we are working in a microservice-based architecture, we have got different microservices, one being the Authentication Service. This is one of the backend services that’s written in .NET Core API, which retrieves a user claim based on a reference token in a domain cookie and generates a JWT for this claim.

If a valid JWT is returned, the call is forwarded to the corresponding service, and the JWT is passed in the request header as an OAuth bearer token for further authorization by the backing service.

JWT auth sequence represented in a flowchart.

The above sequence can be simplified using the following diagram:

Simplified representation of JWT auth sequence, courtesy of TopTal.

Let’s start building something similar now.

Prerequisites

As I said, this is a MERN stack application that we are going to build. The obvious requirements are:

I guess that’s enough for now.

Creating an Express.js server

Here’s the simplest form of code: index.js that says Hello World! on the browser when you open the localhost on port 3000 after running node index:

const express = require("express");
const app = express();
const port = 3000;

app.get(“/”, (req, res) => res.send(“Hello World!”));

app.listen(port, () => console.log(Example app listening on port ${port}!));

Generating JWT on the server

We gotta start with things like a JWT-based Authentication and try to validate stuff. jwt.io gives you the list of different libraries that support different features of JWT. I tried finding the one that supports everything, but there’s none in JavaScript.

Let’s think about what we need here. The backend should be able to support the algorithms HS512 and RS512, as these are recommended by a few banking clients of ours.

Choosing the right library!

I have planned to use the jsrsasign, as that’s the closest to what my backend team generated, and it supports all the algorithms. Let’s include them this way, but don’t forget to install the package locally before you run the code:

const JSRSASign = require(“jsrsasign”);

Implementing the encoding function

The JWT payload is commonly referred as claims — not sure why. So let’s create a claims first:

const claims = {
Username: “praveen”,
Age: 27,
Fullname: “Praveen Kumar”
}

That’s everything I need for my application for now from the Users DB. I am not covering any database concepts here, so I am skipping them off. Next is the private key — as the name says, let’s keep it private and not use it in any of our client-side code.

const key = “$PraveenIsAwesome!”;
Note: Keep this thing absolutely secret!

Now we have got our claims and key ready. Let’s start signing it and generating the token. We have to identify which algorithm we are going to use before signing. These details will be in header. So, let’s create a header.

const header = {
alg: “HS512”,
typ: “JWT”
};

We will be using the HS512 (HMAC-SHA512) algorithm for signing the token, and we are going to generate a JWT. The other types you can generate or existing standards are:

  • JSON Web Signature (JWS): RFC7515
  • JSON Web Encryption (JWE): RFC7516
  • JSON Web Key (JWK): RFC7517
  • JSON Web Algorithms (JWA) – RFC7518
  • JSON Web Token (JWT): RFC7519
  • JSON Web Key (JWK) Thumbprint: RFC7638
  • JWS Unencoded Payload Option: RFC7797
  • CFRG Elliptic Curve Signatures (EdDSA): RFC8037

Let’s carry on with the next main process of generating the token. We have got all we need: header, payload (claims), and key. We need to stringify the above objects and send to the generator.

var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(claims);

The key is already a string, so don’t bother stringifying it. To generate the key, we should pass the three strings along with the algorithm to the JSRSASign.jws.JWS.sign() function like this:

const sJWT = JSRSASign.jws.JWS.sign(“HS512”, sHeader, sPayload, key);

The output you get is a JWT! You can verify the correctness at jwt.io. Finally, logging the string to the console will get you the output:

console.log(sJWT);

And when I executed the above code, I got this:

​​eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA

Putting it into the online validator along with my signature, I got the same output with a signature-verified message:

Signature verified.

Woohoo! You can actually check it out here.

Decoding and validating JWT

The first step is to validate the token before decoding and getting to know what’s in it. Validation is only necessary to make sure that the token is not tampered with and that it has been signed with the same private key. This should not be done on the client side. Let’s try to decode the JSON Web Token by implementing the validation function, and then we will decode it.

Implementing the validation function

Now that we have successfully generated the token based on the same algorithm my backend team uses, let’s try to validate it and verify that it works. To validate and verify the token, all we need is the algorithm, key, and the generated token. Let’s crack on with this.

const token = “eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA”;
const algorithm = “HS512”;
const key = “$PraveenIsAwesome!”;

The syntax is a bit weird — reason being, the validator can try to verify in any of the algorithms, so it takes an array of algorithms. The above information will be passed to the JSRSASign.jws.JWS.verifyJWT() function, which takes in the token, key, and {“alg”: [ algorithm ]}, and the syntax is as follows:

console.log(
JSRSASign.jws.JWS.verifyJWT(token, key, {
alg: [algorithm]
})
);

On the console, when I ran this, I got the following:

true

Ah, that’s a sweet success. Let us continue to decode the token now. The decoding part is really easy; even a browser on the client side can do it, and that is why my client prefers it for a safe and secure transmission of claims object in a single-page application framework like React.

Implementing the decoding function

To decode the JSON Web Token, we will be just passing the second part of the token. This contains the payload, or claims. We will also be requiring the algorithm, and that can be taken from the first part of the token.

The first thing we need to do is split the token on ., then convert it into an array and get the Header and Claim:

const aJWT = sJWS.split(“.”);
const uHeader = JSRSASign.b64utos(aJWT[0]);
const uClaim = JSRSASign.b64utos(aJWT[1]);

Let’s make them uHeader and uClaim because they are untrusted at this moment. Now let’s parse them. The JSRSASign.b64utos() will convert the untrusted Base64 to string, provided by the same library. We’ll now be using the function JSRSASign.jws.JWS.readSafeJSONString(), which is similar to JSON.parse() but has some more exception handling mechanisms.

const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader);
const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim);

Now we have got the parsed header and claims. Let’s try to log them and see the output.

console.log(pHeader);
console.log(pClaim);

Woohoo! We have got the decoded versions here.

{
“alg”: “HS512”,
“typ”: “JWT”
}
{
“Username”: “praveen”,
“Age”: 27,
“Fullname”: “Praveen Kumar”
}

Here we go! Now we can access the payload securely (well, at least not in plaintext) this way! 

Complete code and next part

Here’s the complete code that includes signing, generating, validating, verifying, and decoding! 

const JSRSASign = require(“jsrsasign”);

// Generation
const claims = {
Username: “praveen”,
Age: 27,
Fullname: “Praveen Kumar”
};
const key = “$PraveenIsAwesome!”;
const header = {
alg: “HS512”,
typ: “JWT”
};

const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(claims);
// Generate the JWT
const sJWT = JSRSASign.jws.JWS.sign(“HS512”, sHeader, sPayload, key);
// Log it to the console.
console.log("JSON Web Token: ", sJWT);

const token =
“eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA”;
const algorithm = “HS512”;

// Log it to the console.
console.log(
"Verification: ",
// Validation
JSRSASign.jws.JWS.verifyJWT(token, key, {
alg: [algorithm]
})
);

// Decoding
const sJWS = token;
const aJWT = sJWS.split(“.”);
const uHeader = JSRSASign.b64utos(aJWT[0]);
const uClaim = JSRSASign.b64utos(aJWT[1]);
const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader);
const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim);
// Decoded objects.
// Log it to the console.
console.log("Header: ", pHeader);
console.log("Claim: ", pClaim);

The above code will give you an output like this:

➜  MockAPIServer node dec-enc.js
JSON Web Token: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA
Verification: true
Header: { alg: ‘HS512’, typ: ‘JWT’ }
Claim: { Username: ‘praveen’,
Age: 27,
Fullname: ‘Praveen Kumar’ }

By the way, what I have done is just a server-side generation component and doesn’t include the “web server” to send you the token using a POST request.

Now that we have completed our work on the server side, let’s start building the REST API endpoints for the client to consume.

The logic runs on the server side, and we need to have an interface that helps us to consume the logic. So we will be creating a REST API-based Express.js server. The server will expose a few endpoints for signing in and authenticating the users.

The steps are simple. We need to first export the functions so that we can import them into our main Express.js server script. Then, we need to expose a few REST endpoints that accept HTTP GET and POST requests, preferably one for signing in, one for getting an existing token, and one for signing out. We will also expose one more endpoint to make use of the Bearer authentication headers.

Bearer authentication

The term Bearer authentication, sometimes called token authentication, is an HTTP authentication scheme that involves some secret strings or security tokens called Bearer tokens. The name “bearer authentication” can be understood as “give access to the bearer of this token.”

The Bearer token is a cryptographically generated string, usually generated by the server when a login request is made by the client. Every time the client tries to access resources that require authentication, it must send this token in the Authorization header:

Authorization: Bearer <token>

This scheme was originally created as part of OAuth 2.0 in RFC 6750. Like Basic authentication, Bearer authentication should only be used over HTTPS (SSL).

Using Bearer in front of <token>

The most common question that comes to anyone’s mind concerns the reason behind using Bearer in front of the <token>. Why not simply:

Authorization: <token>

It’s definitely a valid question for most developers. The Authorization: <type> <credentials> pattern was introduced by the W3C in HTTP 1.0 and has been reused in many places since. Many web servers support multiple methods of authorization. In those cases, sending just the token isn’t sufficient. Long before Bearer authorization, this header was used for Basic authentication.

For interoperability, the use of these headers is governed by W3C norms, so even if you’re reading and writing the header, you should follow them. Bearer distinguishes the type of authorization you’re using, so it’s important. Google describes it as a Bearer Token is set in the Authorization header of every inline action HTTP request. For example:

POST /rsvp?eventId=123 HTTP/1.1
Host: praveen.science
Authorization: Bearer Prav33nIsGr3a7JK
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/1.0 (KHTML, like Gecko; Gmail Actions)

rsvpStatus=YES

The string Prav33nIsGr3a7JK in the example above is the Bearer authorization token. This is a cryptographic token produced by our application.

Verification of Bearer tokens

If using Bearer tokens, verify that the request is coming from the trusted domain (say, your client application location) and is intended for the the sender domain. If the token doesn’t verify, the service should respond to the request with the HTTP response code 401 (Unauthorized).

Exporting to REST endpoint

Since we have the code in dec-enc.js, we need to export the right functions. At the end of the file, let’s add some export statements and make a few changes to the way it works with the REST endpoint. The few things we will be doing with this approach are converting the claims, key, and header to be parameters of the encode function.

Changes to the core function

In this section, we will be only dealing with the dec-enc.js. We have a lot of console.log()s, and we need to remove them at any cost since they might leak out some rather sensitive data to the server logger (that is, if someone gains access to it). The next thing would be splitting the code into different units and functions and exporting everything, one by one.

Removing the logs

This process should be fairly easy — just find all the console.log statements and remove them. We have added them earlier just for debugging purposes and not for anything else. The modified code looks something like the below.

const JSRSASign = require(“jsrsasign”);

// Generation
const claims = {
Username: “praveen”,
Age: 27,
Fullname: “Praveen Kumar”
};
const key = “$PraveenIsAwesome!”;
const header = {
alg: “HS512”,
typ: “JWT”
};

const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(claims);
// Generate the JWT
const sJWT = JSRSASign.jws.JWS.sign(“HS512”, sHeader, sPayload, key);

const token = “eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJVc2VybmFtZSI6InByYXZlZW4iLCJBZ2UiOjI3LCJGdWxsbmFtZSI6IlByYXZlZW4gS3VtYXIifQ.Nut6I57FYUGP973LgfOUNUBjMlaIm6NKk8ffgX4BTdQ_Wc2ob8m6uOwWlgoNMxpuRQaOoeFQOHhrIOJ_V8E-YA”;
const algorithm = “HS512”;

// Decoding
const sJWS = token;
const aJWT = sJWS.split(“.”);
const uHeader = JSRSASign.b64utos(aJWT[0]);
const uClaim = JSRSASign.b64utos(aJWT[1]);
const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader);
const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim);

Since we are getting everything from the user or the server, we have to convert the above into callable units or functions. The two functions that are required here are one to generate the JWT and another to verify the JWT. This makes us export just two functions at the end, and we will be exporting using module.exports.Variable and importing using require.

Functions to generate, verify, and validate JWT

The first function we will be writing takes in a few parameters, like claims, key, and header, and returns us a JWT. One thing we need to be clear about is where to define what. For example, where will the constants like the key and header go? The scope of the dec-enc.js is to provide a generic function to generate a JWT and verify a JWT.

So let’s keep it generic — no hardcoding of the above. Let’s create the three functions for them. Since we are using Node.js as the back end, let’s go with ES6 fat arrow functions since they are far more efficient and useful in certain cases than traditional functions. But before all that, let’s start with importing the required jsrsasign (i.e., JS RSA Sign) library.

const JSRSASign = require(“jsrsasign”);

const GenerateJWT = () => {};

const DecodeJWT = () => {};

const ValidateJWT = () => {};

module.exports = {
GenerateJWT,
DecodeJWT,
ValidateJWT
};

In the above code, some people might get confused on the exports part. The last section of code starting with module.exports can be explained this way: the module is a built-in object that is used to describe the module, and it has a parameter exports, which can be assigned multiple times with the named variables we want to export.

The object looks a bit different, too. It makes use of the object literal property value shorthand. This is yet another syntactic sugar in ES6. Consider the following ES5 fragment:

{ GenerateJWT: GenerateJWT, DecodeJWT: DecodeJWT, ValidateJWT: ValidateJWT }

With the new shorthand form, this can be rewritten as the following:

{ GenerateJWT, DecodeJWT, ValidateJWT }

As you can see, this works because the property value has the same name as the property identifier. This a new addition to the syntax of Object Initialiser (section 12.1.5) in the latest ECMAScript 6 draft Rev 13. And yeah, just like the limitations set from ECMAScript 3, you can’t use a reserved word as your property name.

So with the above said, we will be writing the body of the two functions from what we have done before, and the function parameters are going to be the inputs in this case. Let’s crack on writing the functions here:

const JSRSASign = require(“jsrsasign”);

const GenerateJWT = (header, claims, key) => {
// Let’s convert everything into strings.
const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(claims);
// Generate the JWT and return it to the caller.
const sJWT = JSRSASign.jws.JWS.sign(“HS512”, sHeader, sPayload, key);
return sJWT;
};

const DecodeJWT = sJWS => {
const aJWT = sJWS.split(“.”);
const uHeader = JSRSASign.b64utos(aJWT[0]);
const uClaim = JSRSASign.b64utos(aJWT[1]);
const pHeader = JSRSASign.jws.JWS.readSafeJSONString(uHeader);
const pClaim = JSRSASign.jws.JWS.readSafeJSONString(uClaim);
return pClaim;
};

const ValidateJWT = (header, token, key) => {
return JSRSASign.jws.JWS.verifyJWT(token, key, header);
};

module.exports = {
GenerateJWT,
DecodeJWT,
ValidateJWT
};

Exporting and importing the functions

The module.exports, or exports, is a special object that should be present in JavaScript files that require the exporting of resources in a Node.js application. module is a variable that represents the current module, and exports is an object that will be exposed as a module. So, whatever you assign to module.exports or exports will be exposed as a module.

module.exports = {
GenerateJWT,
DecodeJWT,
ValidateJWT
};

In the above code, we are exporting GenerateJWT, DecodeJWT, and ValidateJWT from this file. We will be able to import the functions by using the following code:

const DecEnc = require(“./dec-enc.js”);

We can use them in a few different ways. One common way is to use the parent object, something like this:

DecEnc.GenerateJWT();
DecEnc.DecodeJWT();
DecEnc.ValidateJWT();

The other way is to use named exports and object destructuring assignment, which can be destructured as follows:

const { GenerateJWT, DecodeJWT, ValidateJWT } = require(“./dec-enc.js”);

The above method will be easier because we know the names of the functions, and they are the only ones that we will be using.

Creating the REST API endpoint

Let’s start by creating a basic Express.js server, app.js. We can very much use the Express “Hello World” example to start with.

Starting with requiring the express and defining a port address for the app to listen to, we create an instance of the Express.js server by calling express() and storing the returned value inside a variable named app. We then define the routes one by one:

const express = require(‘express’);
const app = express();
const port = process.env.PORT || 3100;

app.get(‘/’, (req, res) => res.send(‘Hello World!’));

app.listen(port, () => console.log(Server listening on port ${port}!));

When you run node app, the default route would show us Hello World! on your browser when you open the webpage http://localhost:3100, or as defined in the PORT environment variable.

If that variable is undefined, the server application falls back to port 3100 by using an OR condition represented by || and gets stored in the port constant. Finally, we make the server to listen to the port in the last line.

With the above done, let’s also include the three functions from our dec-enc.js. Let’s keep all the require statements together and have the library requires on the top, followed by our custom requires. Adding this to the above code will get our app.js looking like the following:

const express = require(‘express’);
const { GenerateJWT, DecodeJWT, ValidateJWT } = require(“./dec-enc.js”);

const app = express();
const port = process.env.PORT || 3100;

app.get(‘/’, (req, res) => res.send(‘Hello World!’));

app.listen(port, () => console.log(Server listening on port ${port}!));

Let’s carry on by creating the routes for the API calls.

Creating the right routes

For a REST endpoint that gets connected to a React application, it is always better to use a common route prefix of /api/ for all the calls. And since the calls are neither idempotent operators nor contain insensitive data, it is always better to use POST methods here. This will not pass the data to the server through query string, but by content body.

Express.js cannot handle content inside POST data. To parse or read the POST data, we need to use a Node.js body-parsing middleware. express.json() is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser. This middleware is activated using the following new line after defining the app.

Also, let’s update the default home root (/) with a proper message that explains them to use this in the right way. For illustration purposes, the below code doesn’t have the implementation of the API routes.

const express = require(“express”);
const { GenerateJWT, DecodeJWT, ValidateJWT } = require(“./dec-enc.js”);

const app = express();
app.use(express.json());
const port = process.env.PORT || 3100;

const welcomeMessage =
“Welcome to the API Home Page. Please look at the documentation to learn how to use this web service.”;

app.get(“/”, (req, res) => res.send(welcomeMessage));

app.post(“/api/GenerateJWT”, (req, res) => res.send(“”));
app.post(“/api/DecodeJWT”, (req, res) => res.send(“”));
app.post(“/api/ValidateJWT”, (req, res) => res.send(“”));

app.listen(port, () => console.log(Server listening on port ${port}!));

The app.use(express.json()) returns middleware that only parses JSON and only looks at requests where the Content-Type header matches the type option. This parser accepts any Unicode encoding of the body and supports automatic inflation of gzip and deflate encodings.

A new body object containing the parsed data is populated on the request object after the middleware (i.e., req.body) or an empty object ({}) if there was no body to parse, the Content-Type was not matched, or an error occurred.

Implementing the routes

All we need to do now is to call the three methods with the right parameters from the user’s request object. The request (req) object will contain the information sent by the client browser or user to the server, and if we need to send something back to the client browser or the user, we will use the response (res) object. The req.body will have all the information that’s needed for each call.

const express = require(“express”);
const { GenerateJWT, DecodeJWT, ValidateJWT } = require(“./dec-enc.js”);

const app = express();
app.use(express.json());
const port = process.env.PORT || 3100;

const welcomeMessage =
“Welcome to the API Home Page. Please look at the documentation to learn how to use this web service.”;

app.get(“/”, (req, res) => res.send(welcomeMessage));

app.post(“/api/GenerateJWT”, (req, res) =>
res.json(GenerateJWT(req.body.header, req.body.claims, req.body.key))
);
app.post(“/api/DecodeJWT”, (req, res) =>
res.json(DecodeJWT(req.body.sJWS))
);
app.post(“/api/ValidateJWT”, (req, res) =>
res.json(ValidateJWT(req.body.header, req.body.token, req.body.key))
);

app.listen(port, () => console.log(Server listening on port ${port}!));

Testing the API endpoints

Each of the three functions GenerateJWT, DecodeJWT, and ValidateJWT returns a valid JavaScript object. So, we can blindly send the response into a JSON format by calling res.json() and passing the return value of the functions. This will be formatted into JSON and sent to the browser. This can be used by any client that accepts a Content-type: application/json response, like a React application.

To make sure these work right, we can use a free tool called Postman, which is a complete API development environment. This tool helps us to test API endpoints and examine responses.

API requests using Postman.

With Postman, we can add the URL, set the HTTP request method, add the headers, and execute the request to find the right output to be displayed and verified. Now that we have completed our work on the server side, generating the tokens, sending data to client through REST endpoints, let’s build the client side to get our token and decode it.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

What is the MERN stack and how do I use it?

Full Stack Developers: Everything You Need to Know

MERN Stack Tutorial - Building a Todo Application

MERN Stack Tutorial - Build a MERN App From Scratch ❤

Getting Started with the MERN Stack

#node-js #mongodb #reactjs #express #javascript

Learn how to build a full-stack MERN App using JWT authentication
67.05 GEEK