DEV Community

Cover image for Creating a full-stack MERN app using JWT authentication: Part 2
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Creating a full-stack MERN app using JWT authentication: Part 2

Written by Praveen Kumar✏️

So far, we have covered the core functionality of JWT authentication in the first part of this series: “Creating a full-stack MERN app using JWT authentication: Part 1.”

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.

LogRocket Free Trial Banner

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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

{ GenerateJWT, DecodeJWT, ValidateJWT }
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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}!`));
Enter fullscreen mode Exit fullscreen mode

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}!`));
Enter fullscreen mode Exit fullscreen mode

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}!`));
Enter fullscreen mode Exit fullscreen mode

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}!`));
Enter fullscreen mode Exit fullscreen mode

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
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.


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post Creating a full-stack MERN app using JWT authentication: Part 2 appeared first on LogRocket Blog.

Top comments (0)